Geometry Class Hierarchy
There are additional geometry libraries which may be of use for more specialized uses:
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.
P or N or temperature)
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).
v and life attributes to control birth/death of a simple particle systemThe 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.
int GB_Attribute::addIndex(const char *str) void GB_Attribute::renameIndex(int idx, const char *str) index int GB_Attribute::destroyIndex(const char *str) void GB_Attribute::clearIndex() void GB_Attribute::removeRedundantIndex() int GB_Attribute::getIndexSize() int GB_Attribute::getIndex() const char * GB_Attribute::getIndex() NULL if no string is associated with a given index.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.
GEO_Primitive::evaluatePoint() has three different signatures each which stores the result in a different fashion:evaluatePoint(GEO_Vertex &result, GEO_AttributeHandleList &h, ...) evaluatePoint(UT_Vector4 &result, ...) P attribute.evaluatePointWAttrib(UT_Vector4 &result, GB_AttributeData &adata, ...) P attribute in the result parameter, and all attributes in the adata data. The adata parameter must be allocated and deleted by the caller and must have the correct amount of storage allocated 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. }
(name, type, size). This means that it's possible to have two attributes that have the same name but differ in type or size. This is non-intuitive and can lead to confusion. GB_AttributeRef attrib;
float one[3] = {1,1,1};
// Add "float Cd[3]" attribute
attrib = GU_Detail::addPointAttrib("Cd", sizeof(float)*3,
GB_ATTRIB_FLOAT, one);
float Cd[3] and you try to add a vertex attribute float Cd[3], then, the point attribute will be converted/promoted to a vertex attributeThere 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).
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.
The sub-classes are
The tube primitive has two additional intrinsic parameters:
radius = 1 + (taper - 1)*v
v coordinate. So, with a taper of 1, the tube is a cylinder. With a taper of 1, the tube becomes a cone (with the apex at v == 1 ).Of course, this is the radius prior to transformation by the rotation/scale matrix.
v. The first third for the bottom end-cap, the last third for the top end cap.
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.
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);
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); }
In all metaball functions, r is the radius squared:
fpreal wyvill(fpreal r) { return -4/9*r^3 + 17/9*r^2 - 22/9*r + 1 } fpreal elendt(fpreal r) { return -4/3*r^2 + 34/9*r + 1 } fpreal blinn(fpreal r) { const fpreal blinn_radius = 9; const fpreal blinn_min = SYSexp(-blinn_radius); const fpreal blinn_scale = 1/(1 - blinn_min); return (SYSexp(-blinn_radius*r) - blinn_min)*blinn_scale; } fpreal links(fpreal r) { if (r < 1/9) return 1 - 3*r; fpreal r2 = SYSsqrt(r); return 3/2 * (1-r2)*(1-r2); } fpreal hart(fpreal r) { // Provided by John Hart fpreal r2 = SYSsqrt(r); return -6*r^7 + 15*r^5 - 10*r^3 + 1; } fpreal prman(fpreal r) { return -r^3 + 3*r^2 - 3*r + 1; }
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.
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:
min() function returns the minimum density of the argumentsmax() function returns the maximum density of the argumentssum() function returns the sum of the arguments
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);
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);
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); }
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.
method. Multiple details can be merged by specifying GB_COPY_ADD for method except for the first and last detail which should use GB_COPY_START and GB_COPY_END respectively.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.
1.5.9