All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
POP Concepts

POP Data

POP nodes, like SOP nodes, process a geometry detail (GU_Detail). POP nodes differ from SOP nodes, in that a chain of POPs will iteratively update a single geometry detail (POP_ContextData::getDetail()). This geometry detail can be thought of as the particle system. Typically, the particle system will consist of one or more particle primitives (GEO_PrimParticle), though it may also contain other primitive types.

POPs Processing

POPs discretize a particle system into a series of snapshots. An external entity (e.g. Popnet SOP, particle viewer) instantiates a POP_ContextData, initializes the particle system to some initial state, and then repeatedly cooks the POP network terminating at a given POP node (via POP_ContextData::cook(OP_Context &)), advancing the time between cooks by some fixed increment. This same external entity will then continue cooking the POP network by increments from the last cooked state when it wants to advance the state of the particle system to a later time.

Typically each such pop network cook begins by advancing the state of the particle system from the previous discrete snapshot. We refer to this update as a timestep. This is followed by cooking each POP in the network, starting from the inputs and working down until we reach the terminal node in question, generally the one with the cook flag. Cooking each POP entails calling that node's virtual POP_Node::cookPop(OP_Context &context) method. The POP_ContextData containing the GU_Detail to modify is accessible by calling OP_Context::getData() on the supplied OP_Context object. The individual cookPop() methods will modify the state of the supplied particle system in some way: birthing particles, modifying the state of existing particles, creating groups, etc.

Cooking A POP Network

Any code that wants to cook a POP network requires three things:

For the purposes of this discussion, we assume we have some entity that is asked for the cooked state of a POP network at successive times. All variables prefixed with my (ex. myDetail) in the following code snippets are assumed to be persistent member variables of some object.

First, during some initialization stage, we'll want to create an empty detail and a POP_ContextData object to use.

myDetail = new GU_Detail();
myContextData = new POP_ContextData("example");

The remainder of the code snippets in this section will demonstrate what is needed in a function that is asked to cook the POP network. We assume that this function has been asked for the cooked state of a POP network at time t. In the following code, part_start is the start time of the particle system and preroll is how long the particle system should already have been cooking at the start time (typical options in such code). frame_inc is the time increment a single frame represents.

One of the first things such code typically does is determine whether it can continue cooking from where it last left off, or if it needs to reset the simulation and recook from the very start.

Quite often, this will depend on the time the caller specifies.

// Typically, we want to reset whenever asked for the simulation either at
// the start of the current playbar range, or the at first frame. However,
// if this code is inside an OP that would have been explicitly reset by
// an output driver, that output driver may request that we ignore the
// playbar when determining whether a reset is required by setting a flag
// in the OP_Director.
if( OPgetDirector()->skipPlaybarBasedSimulationReset() )
if ((t >= part_start && t < part_start + frame_inc))
need_reset = true;
float range_start, range_end;
chman->getSampleRange(range_start, range_end);
if ((t >= part_start && t < part_start + frame_inc) ||
UTequalZero(chman->getTime(range_start) - t) ||
UTequalZero(chman->getGlobalStart() - t))
need_reset = true;

We'll also need to reset if we're being asked to cook a different POP.

if (myPrevPopId != pop->getUniqueId())
need_reset = true;

To perform the actual reset, we call POP_ContextData::reset(). If a reset is not required, we still need to check whether the network checksum for the POP network rooted at pop has changed. Any such change may require addition of new attributes, achieved through POP_Node::addAllAttribs(). An explicit call to POP_Node::addAllAttribs() is unnecessary when a reset is performed as POP_ContextData::reset() already takes care of this.

unsigned long netchecksum;
if (need_reset)
myContextData->reset(pop, myDetail, "", false, 0, false);
myLastCookTime = part_start - preroll - 1000.0f;
netchecksum = pop->computeNetChecksum();
else if (myPrevPopNetChecksum != (netchecksum = pop->computeNetChecksum()))

We can ignore any time that occurs before the start time of the particle system.

if (t < part_start)
goto done;

Our persistent POP_ContextData requires some settings to be reset or updated from the values used during earlier cooking.

myContextData.myRemoveUnused = 1; // may be an option
myContextData.myMaxParticles = 0; // may be an option
myContextData.myXformObj = NULL;
myContextData.myCookPOP = pop;
myContextData.myDoInfoButton = POPNET_Node::ourDoInfoFlag;
myContextData.myDoUpdateViewport = 0; // may be an option

Set up an OP_Context, necessary for cooking any node.

OP_Context context(t);

Finally, cook the POP network up to the specified time.

float time_delta = frame_inc * POP_TIMEDELTA;
float cook_start, cook_time;
cook_start = part_start - preroll;
if (myLastCookTime < cook_start)
myLastCookTime = cookStart;
cook_time = myLastCookTime;
for ( ; cook_time <= t + time_delta ; cook_time += frame_inc)

Lastly, we need to remember some things for the next time we're called.

myLastCookTime = cook_time;
myPrevPopId = pop->getUniqueId();
myPrevPopNetChecksum = netchecksum;