[SESI logo]

Houdini Development Toolkit - Version 6.5

Side Effects Software Inc. 2004

Particle Operators

Creating a custom modifier POP

Introduction

POPs are Particle Operators and are used to modify particle systems. To understand how to write a custom POP, it is best to study a simple example.

This example POP will change the particle's color to be brighter the closer it is to a point in 3D space. It is a simple example of a modifier POP that changes a particle's attributes. It will demonstrate the general structure of a POP.

Anatomy of a modifier POP

This example can be found in the POP toolkit examples. #include <UT/UT_DSOVersion.h> #include <UT/UT_Color.h> #include <GEO/GEO_PrimPart.h> #include <GEO/GEO_Point.h> #include <GU/GU_Detail.h> #include <PRM/PRM_Include.h> #include <OP/OP_Operator.h> #include <OP/OP_OperatorTable.h> #include "POP_SpotLight.h" The UT_Color class is needed because it will be used to convert values between different color spaces. Aside from that, all of these header files are required to compile a custom POP. static PRM_Name names[] = { PRM_Name("center", "Center"), PRM_Name(0) }; PRM_Template POP_SpotLight::myTemplateList[] = { PRM_Template(PRM_FLT_J, 1, &POPactivateName, PRMoneDefaults, 0, &PRMunitRange), PRM_Template(PRM_STRING, 1, &POPsourceName, 0, &POP_Node::pointGroupMenu), PRM_Template(PRM_XYZ_J, 3, &names[0]), PRM_Template() }; Most POPs have an activation field and a source group parameter. The activation field is used to turn the entire POP off and on. If the parameter evaluates to 0 or less, nothing is processed. The source group allows the user to apply the POP only to a specific group of points. Although these fields are not absolute requirements, it may be a good idea to follow this style as it is already used by most other POPs.

The third parameter represents the position in space that will be used as a center of brightness.

OP_TemplatePair POP_SpotLight::myTemplatePair (myTemplateList, &POP_LocalVar::myTemplatePair); OP_VariablePair POP_SpotLight::myVariablePair (0, &POP_LocalVar::myVariablePair); There are a lot of local variables built into POPs that represent particle attributes. These are handled by the POP_LocalVar class. In order to use them, they must be "inherited" using the template and variable pair methodology. Again, it is not imperative that a custom POP inherit from the POP_LocalVar class (They can inherit from POP_Node instead). However, most modifier POPs will be far more useful if they take advantage of the local variables already defined in POPs. void newPopOperator (OP_OperatorTable* table) { table->addOperator( new OP_Operator("spotlight", // Name "SpotLight", // English POP_SpotLight::myConstructor, // "Constructor" &POP_SpotLight::myTemplatePair, // simple parms 1, // MinSources 1, // MaxSources &POP_SpotLight::myVariablePair));// variables } This new operator must be added to the operator table. OP_Node* POP_SpotLight::myConstructor (OP_Network* net, const char* name, OP_Operator* entry) { return new POP_SpotLight(net, name, entry); } POP_SpotLight::POP_SpotLight (OP_Network* net, const char* name, OP_Operator* entry) :POP_LocalVar (net, name, entry) { myIndirect = allocIndirect(sizeof(myTemplateList) / sizeof(PRM_Template)); } POP_SpotLight::~POP_SpotLight (void) { } The POP doesn not hold any data that needs to be created and destroyed in the constructors and destructors. Every POP ultimately is a POP_Node, but note that this POP inherits from the POP_LocalVar class. The POP_LocalVar class in turn inherits from POP_Node.

The call to allocIndirect() creates an array of values used to store parameter indices. These indices are used during parameter evaluation.

OP_ERROR POP_SpotLight::cookPop (OP_Context& context) { POP_ContextData* data = (POP_ContextData*) context.myData; The cookPop() method is the heart and soul of a POP. It is here that the POP modifies the particle system passed into it. Unlike other operator types, POPs themselves don't contain any data. Instead, the geometry detail to modify is passed in through the context. The detail represents the state of the particle system. So, it is the job of the POP to take a state (or detail) and update it for the current time.

All of the POPs specific information is passed into the cook method using the myData field on the OP_Context as a POP_ContextData. The POP_ContextData holds a lot of persistent information used to cook the particle system. This includes the detail itself, the offsets of many attributes, the transform object, and the time increment.

float t = context.myTime; GEO_PrimParticle* part; GEO_ParticleVertex* pvtx; GEO_Point* ppt; POP_FParam centerx; POP_FParam centery; POP_FParam centerz; Variables are allowed in most POP fields. For the sake of efficiency, if these variables are not dependent on local variables and thus do not change per-particle, they shouldn't still be re-evaluated over and over again. To solve this, parameter values are cached if they don't change per particle. The POP_FParam type is actually just a function pointer. If the parameter is variable dependent, this function will be the function usually used to evaluate the parameter. Otherwise, it will be a function that just returns a cached value. There are macros that help set up the function pointers further down. UT_String sourceName; GB_PointGroup* sourceGroup = NULL; if (lockInputs(context) >= UT_ERROR_ABORT) return(error()); The inputs should be locked. This ensures that all POPs that this POP is dependent on are cooked. setupDynamicVars(); Right at the beginning of the cook, dynamic variables need to be set up in the POP_LocalVar class. This will enable all user defined variables to be used in the POP. if (buildParticleList(context) >= UT_ERROR_ABORT) goto done; Each POP needs to know which particles to act on. This is usually done in one of two ways: from the input wires and from an input group. Each wire that goes into a POP represents a set of particle primitives and this list is stored in a particle list. A call to the POP_Node method buildParticleList() builds this list. The list is stored in the POP_Node variable, myParticleList. if (data->isGuideOnly()) goto done; If parameters are being changed in the POP viewer, but the framebar is not playing, the POP is recooked, but only the guide geometry needs to be updated. So, if the POP is being recooked just for the guide geometry, we can short circuit the cooking since this POP doesn't have any guide geometry. addDiffuseAttrib(data); This POP will modify the diffuse color attribute of the particle. The function addDiffuseAttrib() will ensure that this point attribute exists. The POP_Node class contains many methods to add point attributes. if (!checkActivation(data, (POP_FParam) ACTIVATE)) goto done; The activation field determines whether the POP is active or not. If it isn't, the processing can be short circuited before doing any more work is done. The checkActivation() method will take care of allowing certain local variables in the field. SOURCE(sourceName); if (sourceName.isstring()) { sourceGroup = parsePointGroups((const char*) sourceName, data->getDetail()); if (!sourceGroup) { addError(POP_BAD_GROUP, sourceName); goto done; } } As stated earlier, the particles to act on can be given by a point group. This group is built using the parsePointGroups() method of POP_Node. setupVars(data, sourceGroup); The setupVars() method found in POP_LocalVar needs to be called to set up attribute offsets that are used when evaluating local variables. POP_FCACHE(centerx, CENTERX, getCenterX, myCenterX); POP_FCACHE(centery, CENTERY, getCenterY, myCenterY); POP_FCACHE(centerz, CENTERZ, getCenterZ, myCenterZ); POP_FCACHE is one of the macros that are used to set up the parameter evaluation function pointer. Here is the macro: #define POP_FCACHE(var, eval, get, myvar) \ if (isVarDependent(data->getDetail(), (POP_FParam) eval)) \ var = (POP_FParam) eval; \ else \ { myvar = eval(t); var = (POP_FParam) get; } If the parameter is variable dependent, the macro sets the function pointer to the regular evaluation function which in this case is CENTERX (defined in POP_SpotLight.h). Otherwise, it evaluates the parameter once, stores it in a cache variable and the function pointer used is an internal method that just returns the cached value. The internal method and the cache value are set up in the header file. myCurrIter = 0; The POP_LocalVar class uses two variables to determine which iteration and which point is currently being processed: myCurrIter and myCurrPt respectively. For the class to properly evaluate local variables, these two values must be updated as the points are processed. if (sourceGroup) { FOR_ALL_GROUP_POINTS(data->getDetail(), sourceGroup, myCurrPt) { changePoint(myCurrPt, data, t, centerx, centery, centerz); myCurrIter++; } } else { for (part = myParticleList.iterateInit() ; part ; part = myParticleList.iterateNext()) { for (pvtx = part->iterateInit() ; pvtx ; pvtx = pvtx->next) { myCurrPt = pvtx->getPt(); changePoint(myCurrPt, data, t, centerx, centery, centerz); myCurrIter++; } } } The easiest way to process particles is to have a single method that modifies the particle. Here, this method is called changePoint(). This method is then applied to the point group if a source group is being used or to the particle list otherwise. unlockInputs(); The inputs were locked at the beginning of the function. They must be unlocked before exiting. myCurrPt = NULL; Sometimes, the UI dialog for a POP needs to be updated (which in turns requires some variable evaluation) so the myCurrPt variable should be reset. return error(); } void POP_SpotLight::changePoint (GEO_Point* ppt, POP_ContextData* data, float t, POP_FParam centerx, POP_FParam centery, POP_FParam centerz) { UT_Vector3 center; UT_Vector3* color; UT_Vector3 d; UT_Vector3 p; UT_Color HSVtoRGB(UT_HSV); UT_Color RGBtoHSV(UT_RGB); float r, g, b; float h, s, v; float d2; The purpose of this function is to modify the passed in point by changing its color. center.assign(POP_PEVAL(centerx), POP_PEVAL(centery), POP_PEVAL(centerz)); Find out the center position. The POP_PEVAL macro evaluates the function pointer. p = ppt->getPos(); d = p - center; color = (UT_Vector3*) ppt->getAttribData(data->getDiffuseOffset()); The POP_ContextData holds the attribute offsets for most attributes used by POPs. RGBtoHSV.setValue(color->x(), color->y(), color->z()); RGBtoHSV.getHSV(&h, &s, &v); d2 = d.length2(); v = UTequalZero(d2) ? 1.0f : 1.0f / d.length2(); if (v > 1.0f) v = 1.0f; HSVtoRGB.setValue(h, s, v); HSVtoRGB.getRGB(&r, &g, &b); The UT_Color class is used to convert the particle's color to HSV space, modify the internsity based on how far the particle is from the center position and change it back to RGB. color->assign(r, g, b); }

Summary

This example demonstrated the general structure of a modifier POP. It covered the following topics which are common to many POPs:
Table of Contents
Operators | Surface Operations | Particle Operations | Composite Operators | Channel Operators
Material & Texture | Objects | Command and Expression | Render Output |
Mantra Shaders | Utility Classes | Geometry Library | Image Library | Clip Library
Customizing UI | Questions & Answers

Copyright © 2004 Side Effects Software Inc.
477 Richmond Street West, Toronto, Ontario, Canada M5V 3E7