|
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:
- Having an activation field to turn POPs on and off.
- Modifying only particles in a source group.
- Local variables from the POP_LocalVar class.
- The list of particle systems to modify held in myParticleList.
- Using the POP_ContextData to retrieve attribute offsets.
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