Geometry Introduction

Table Of Contents

Geometry Class Libraries

The main geometry libraries in Houdini are:

HDK_Geometry_Classes.png

Geometry Class Hierarchy

There are additional geometry libraries which may be of use for more specialized uses:

Geometry Structures

The main classes for Houdini geometry are:

HDK_Geometry_Structure.png

Geometry Structure

This diagram shows the geometry structure for a simple mesh of triangle primitives.

The Detail (GU_Detail) container contains a list of points (GEO_Point) and a list of primitives (GEO_Primitive).

Each primitive has 3 vertices (GEO_Vertex) which refers to one of the shared points (GEO_Point). In this example point 0 and point 3 are shared between the two polygons. All attribute data associated with point 0 and point 3 will be shared between the two polygons. Any attribute on the vertex objects will not be shared.

Introduction To Geometry Attributes

An geometry attribute (GB_Attribute )is defined by:

The list of attributes are maintained by the GU_Detail. Because the attributes are maintained at the global level, attributes are uniform across element types. That is, if one GEO_Point object has the Cd attribute, then all GEO_Point's in the detail will have the Cd attribute.

There are separate lists maintained for each geometric element: GEO_Point, GEO_Vertex, GEO_Primitive and even GU_Detail. Each element type has it's own set of attributes. This means that it's possible to have a primitive and point attribute of the same name, type and size. For example, it's possible to have the float Cd[3] attribute on both primitive and the point objects. It's up to the caller to determine the precedence, though the informal standard is to prefer finer grained attributes over coarser grained attributes (i.e. GEO_Vertex before GEO_Point before GEO_Primitive before GU_Detail).

Example Code

Attribute Types

The enum GB_AttribType lists the different types of attribute data. At the current time, these types are:

String Attributes

Ignore the GB_ATTRIB_STRING enum.

The preferred method for storing string attribute data is to use GB_ATTRIB_INDEX. With an index attribute, each element stores an integer value which is used to look up into a table of strings stored on the attribute.

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_BrushHairLenC.

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_FlattenC 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 Evaluation

There are various methods in the geometry libraries which perform evaluation of attributes. Many of these have three different signatures for evaluation. For example, GEO_Primitive::evaluatePoint() has three different signatures each which stores the result in a different fashion:

The difficulty with using the attribute handle list is that the results need to be stored in a GEO_Vertex. However, it is easy to create temporary GEO_Vertex objects using the GEO_WorkVertexBuffer class.

Here's an example of how you might evaluate interior points of polygon

    void
    samplePoint(GU_Detail &gdp, int prim_number)
    {
        // Create a temporary vertex buffer.
        GEO_WorkVertexBuffer     vbuffer(&gdp);
        GEO_Vertex              *tmp;

        // Create an attribute handle list and bind all the floating
        // point attributes
        GEO_AttributeHandleList hlist;

        hlist.bindDetail(&gdp);
        // Add vertex attributes first (higher priority)
        hlist.appendFloatAttributes(GEO_VERTEX_DICT);
        // Add point attributes next
        hlist.appendFloatAttributes(GEO_POINT_DICT);

        // Get vertex out of the work-vertex buffer.  By passing in a
        // null pointer for the point, a temporary point will be
        // created (to hold the point attributes).
        tmp = vbuffer.appendVertex(NULL);

        // Evaluate the point
        gdp.primitives()(prim_number)->evaluateInteriorPoint(*tmp, hlist, .5, .5);
        for (int i = 0; i < hlist.entries(); i++)
            processAttribute(tmp, hlist[i]);

        // The temporary vertex will be released when the vertex
        // buffer is destructed.
    }

Attribute Caveats

Geometry Primitives

There are several sub-classes of GEO_Primitive. Each primitive has one or more GEO_Vertex objects.

Face Primitives

Face primitives store their vertices in a 1D list. If the face is closed, the last vertex has is periodic and will connect the last vertex in the list to the first vertex.

There are three main sub-classes of the GEO_Face base class. GEO_PrimPoly is a sub-class which uses an implicit linear basis to represent polygons. GEO_Curve gets further refined to GEO_PrimNURBCurve (NURBS curve) and GEO_PrimRBezCurve (rational Bezier curve).

Patch Primitives

Patch primitives store their vertices in a 2D matrix. There are two closed flags indicating whether the primitive is closed in the u or v directions. For example, a grid primitive would be open in both u and v while a tube primitive would be closed in one of the directions and a torus would be closed in both parametric directions.

There are three main sub-classes of the GEO_Hull base class (mirroring Face Primitives). GEO_PrimMesh represents a linear mesh, while GEO_PrimNURBSurf (NURBS surface) and GEO_PrimRBezSurf (rational Bezier surface) can be used to represent higher order surfaces.

Parametric coordinates are well defined for these primitives.

Quadric Primitives

Quadric primitives have a single GEO_Vertex representing the center of the primitive. All primitives also store a 3x3 rotation/scale matrix (the center of the primitive is determined by the GEO_Vertex).

The sub-classes are

The tube primitive has two additional intrinsic parameters:

Of course, this is the radius prior to transformation by the rotation/scale matrix.

See also:
GEO_PrimTube::getTaper(), GEO_PrimTube::setTaper(), Matrix Classes

Volume Primitives

The volume primitive is like a quadric primitive in that it has a single GEO_Vertex and a rotation/scale matrix. The volume primitive also has a scalar voxel field which stores values over the volume.

The volume before the rotation/scale matrix occupies the bounding box (-1, -1, -1) to (1, 1, 1).

More detail on volumes can be found in Volumes in Houdini.

Particle Primitives

The GEO_PrimPart class stores a linked list of GEO_ParticleVertex vertices. This is primarily so that killing particles is a less expensive operation.

Because vertices are stored in a linked list, traversing vertices can be extremely expensive. Consider the loop:

    int         i, nvtx;
    nvtx = prim->getVertexCount();
    for (i = 0; i < nvtx; i++)
        vtx = prim->getVertex(i);
Since the vertices are stored in a linked list, it is O(N2) since to find the Nth vertex requires traversal of the entire list. The particle primitive keeps a one level cache of the last vertex queried and uses this to optimize. But, random queries into primitive vertices should be avoided if possible.

Each GU_Detail may have multiple particle primitives (See: Creating Custom POPs)

Each particle primitive also contains a GEO_PartRender object that specifies how the particles from that primitive should be rendered.

For example, to build a particle system with 4 particles rendered as motion blurred rounded tubes:

    GU_Detail            gdp;
    GU_PrimParticle     *partsys;
    GEO_ParticleVertex  *part;
    float                one = 1;

    if (partsys = GU_PrimParticle::build(&gdp, 4))
    {
        part = partsys->iterateInit();
        do
        {
            // Initially all particles spring from (3,1,1):
            part->getPos().assign(3, 1, 1);
        }
        while (part = partsys->iterateFastNext(part));

        partsys->getRenderAttribs().setType(GEO_PARTICLE_TUBES);
        partsys->getRenderAttribs().setMotionBlur(1)
        partsys->getRenderAttribs().setBlurTime(0.03);
        partsys->getRenderAttribs().setSize(0.05);
    }

Metaball Primitives

The Houdini geometry format supports quadric (GEO_PrimMetaBall) and superquadric (GEO_PrimMetaSQuad) metaballs. Meta-primitives are subclassed from GEO_PrimMeta (as well as GEO_Primitive). Like Quadric Primitives, metaballs have a single vertex and a transform matrix. However, metaballs also have two intrinsic parameters

Superquadrics (http://en.wikipedia.org/wiki/Superquadrics) have two additional intrinsic parameters to control the shape of the superquadric. The XY and Z exponents are used to change the shape of the superquadric.

See also:
GEO_PrimMetaSQuad::setXYexp(), GEO_PrimMetaSQuad::setZexp()

Metaball Expressions

By default, all metaballs in the GU_Detail will blend together by adding their density fields. However, it's possible to make an expression which defines how the metaprimitives are supposed to blend.

The expression uses a simply grammar which is defined http://www.sidefx.com/docs/current/nodes/sop/metaball, but in brief, has 2 primitive types and 3 functions defined:

For example: sum(max("group1", "group2"), max("group3", "group4"), 0, 1)

Internally, the metaball expression is represented using the TS_MetaExpression class. This is an example of how to evaluate the expression:

    void
    dumpExpression(const TS_MetaExpression *expr)
    {
        const char              *function = NULL;
        const TS_ExpressionList *kids = expr->getAllKids();
        const TS_MetaPrimitive  *mprim;
        const GEO_Primitive     *gprim;
        int                      i;

        if (expr->isSum())
            printf("sum(");
        else if (expr->isMin())
            printf("min(");
        else if (expr->isMax())
            printf("max(");

        for (i = 0; i < kids.entries(); i++)
        {
            if (i > 0)
                printf(",");

            // Get the Nth child of the expression
            mprim = kids->operator()(i)->isPrimitive();
            if (mprim)
            {
                // Each TS_Expression has a back-pointer to the GEO_Primitive
                gprim = (const GEO_Primitive *)mprim->getGeoPrimPtr();
                // Print the number of the primitive
                printf("%d", gprim->getNum());
            }
            else
            {
                // Recuse into a new expression
                dumpExpression(kids->operator()(i));
            }
        }
        printf(")");
    }

The expression for a GU_Detail is stored on a detail attribute string metaExpression. You can access this directly:

        TS_MetaExpression       *expr;

        expr = GEO_MetaExpression::getExpression(gdp, NULL, NULL, NULL);
        expr->ref();    // Reference count the expression
        dumpExpression(expr);
        expr->unref();

Alternatively, you can use the GEO_MetaExpression class. With a GEO_MetaExpression, you can easily evaluate attributes for the blended surface:

        GEO_MetaExpression      expr;

        expr.preCompute(gdp);
        dumpExpression(expr.getTSExpression());
        expr.computeAttributes(P, result, handles);

Adding a Primitive

For simple primitives such as polygons, adding a primitive to the detail is usually accomplished by calling a GEO_Detail::appendPrimitive() function. Note that it can either copy a primitive from another primitive, or add an empty primitive of the specified type, which then has to be filled in with data. An example below illustrates an addition of a simple polygon to an existing detail:

void addPolygon(GU_Detail* gdp)
{
    int point_idx;
    const int num_points = 3;
    UT_Vector3 positions[num_points];
    GEO_PrimPoly *prim_poly_ptr;
    GEO_Point* added_points[num_points];

    // Set positions for the points of the polygon
    positions[0].assign(0, 0, 0);
    positions[1].assign(10, 0, 0);
    positions[2].assign(0, 10, 0);

    // Add new points to the gdp. This is optional - you can also
    // reuse existing points and add them to the polygon instead.
    for(point_idx = 0; point_idx < num_points; point_idx++)
    {
        // Append a new point
        added_points[point_idx] = gdp->appendPoint();
        // Set its coordinates
        added_points[point_idx]->getPos().assign(positions[point_idx][0],positions[point_idx][1],
            positions[point_idx][2], 1.0);
    }

    // Append a new polygon primitive. Primitive types are defined in GEO_PrimType.h
    prim_poly_ptr = dynamic_cast<GEO_PrimPoly *>(gdp->appendPrimitive(GEOPRIMPOLY));
    prim_poly_ptr->setSize(0);

    // Append vertices that form this primitive
    for(point_idx = 0; point_idx < num_points; point_idx++)
        prim_poly_ptr->appendVertex(added_points[point_idx]);

    // Close the primitive to make it a polygon instead of a polyline.
    prim_poly_ptr->close();
}

For more complex primtive types, it is often more convenient to use a number of helper builder functions provided in the same GU class which corresponds to the primitive type you would like to build, such as GU_PrimPoly::build(), GU_PrimMesh::build() or GU_PrimSphere::build().

For example, to append a NURBS primitive you can use GU_PrimNURBSurf::build() function which builds the primitive for you, adhering to the specified parameters, and returns a pointer to the new NURB surface. You still need to fill in the points and the knot vector, however:

    GU_PrimNURBSurf *nurb = GU_PrimNURBSurf::build(gdp, num_rows, num_columns,
        u_order, v_order, do_wrap_u, do_wrap_v,
        do_interp_u, do_interp_v, GEO_PATCH_QUADS, do_append_points);
        
    // The knot vectors can be accessed as follows:
    GB_Basis* curr_basis = nurb->getUBasis();
    int knot_vector_len = curr_basis->getLength();
    float* knot_vector = curr_basis->getData();

    // For the points, you can call the standard method on a detail. Please note
    // that the points may not necessarily belong to the NURB you have just
    // created if the gdp contains other primitives.
    UT_Vector4 temp_vec;
    nurb->getParent()->points()[point_index]->setPos(temp_vec);

Groups

Houdini's geometry also supports groups - a named subset of items within the gdp, each marked as belonging to a group. There are several group types, including point, primitive, vertex and edge groups, all of which derive from the base group type GB_BaseGroup.

Most of the geometry manipulation functions in GU_Detail and GEO_Detail have an optional group parameter, which, if not null, will force the function to perform its operations only on items belonging to a specific group instead of working with all the items in a detail. Most of the group operations, such as creating, deleting, and modifying them, can be found in GB/GB_Detail.h.

You can also remove any groups that have become unused by calling GB_Detail::removeUnusedGroups().

An example which shows how to manipulate groups can be found below. Although it uses point groups, operating on primitive groups follows the same principles:

void groupsExample(GU_Detail* gdp)
{
    GB_PointGroup       *point_group;
    GB_PointGroup       *copied_group;
    GB_PointGroup       *found_group;
    const char* const copied_group_name = "__copied_group__";

    // Create a new point group.
    point_group = gdp->newPointGroup("__test_group__", false);

    // Get the first primitive in the detail (this assumes it exists).
    GEO_Primitive* first_prim = gdp->primitives()[0];

    // Add the points of the first primitive to the group.
    first_prim->addPointRefToGroup(*point_group);

    // A more conventional way to add items to groups is by 
    // simply calling the GB_Group::add() function:
    point_group->add(gdp->points()[0]);

    // Make a copy of the group and give it a new name
    copied_group = gdp->copyGroup(*point_group, copied_group_name);

    // Try to find the group we have just copied:
    found_group = gdp->findPointGroup(copied_group_name);
    UT_ASSERT(found_group);

    // You can also clear the group as following:
    found_group->clearEntries();

    // Destroy the group. It can be destroyed either by passing in a pointer,
    // or by passing in a group name.
    gdp->destroyPointGroup(point_group);

    // Destroy the copied group by name.
    gdp->destroyPointGroup(copied_group_name);

}

Edges

Edges are not explicitly represented as elements in GU_Detail and thus do not have peristent attribute data associated with them. The GB_Edge class is used by many functions to describe a directed edge. An instance of this class can be used to represent a directed segment between two points or two vertices of a primitive. For example:

GEO_Point *pt0, *pt1;
GEO_Primitive *prim;
int i;

// ...

// a directed edge from pt0 to pt1
GB_Edge edge1(pt0, pt1);
// a directed edge along a primitive from vertex i to vertex i+1
GB_Edge edge2(prim->getVertex(i).getPt(), prim->getVertex(i + 1).getPt(), prim);

Note when using edge groups, instances of the GB_EdgeGroup class describe a group of directed edges.

Copying Elements Between Details

GU_Detail's base class provides several methods for copying points and primitives between details:

These methods will retain all attribute values for the merged elements. Attributes from the source detail will be created in the destination for the classes of copied elements.


Generated on Mon Jan 28 00:27:57 2013 for HDK by  doxygen 1.5.9