This example is an exact copy of the Blend CHOP found in Houdini. It takes several inputs, a control input and multiple blend inputs. Each track in the control input corresponds to a single blend input. The output is a weighted blend of the tracks in the blend inputs, controlled by the weights in the control clip. The sum of the weights in each control track at a given frame are normalized to one. This method is similar to the one in the Blend SOP.
There is also a differencing mode where the first blend input is assumed to be a rest position, and the control tracks control the weighting of the second and subsequent blend inputs. If these weights sum to less than one, the rest position is weighted so that it completes the blend (weights sum to one). This method is generally used for posing.
Example Walkthrough
These code fragments are taken from CHOP_Blend.C.
These headers are required for any HDK CHOP.
#include <CHOP/CHOP_VariableList.h>
These headers are used by most CHOPs.
These headers are used by this particular CHOP; UT_Interrupt to check for user interruption while cooking.
};
methodItems);
PRM_Name(
"firstweight",
"Omit First Weight Channel"),
};
CHOP_Blend::myTemplateList[] =
{
};
bool
CHOP_Blend::updateParmsFlags()
{
changes |= enableParm("firstweight", GETDIFFERENCE());
return changes;
}
This chunk of code defines the parameter dialog for the blend CHOP. It has two parameters, the first a menu for normal/differencing mode, and the second, a toggle to omit the first weight in differencing mode. The toggle is disabled if not in differencing mode.
CHOP_Blend::myVariableList[] = {
{ 0 }
};
The blend CHOP has no local variables.
const char *name,
{
return new CHOP_Blend(net, name, op);
}
const char *name,
{
}
{
}
The blend CHOP derives directly from CHOP_Node.
CHOP_Blend::getCacheInputClip(int j)
{
return (j>=0 && j<myInputClip.entries()) ?
myInputClip(j) : 0;
}
Because the blend CHOP has two distinct types of input clips, a control clip and blend clips, this private method makes it a little easier to access the blend clips.
{
int num_motion_tracks;
int num_clips;
int difference;
int fweight;
short int percent = -1;
int stopped = 0;
fpreal adjust[2] = {1.0, -1.0};
difference = GETDIFFERENCE();
if(difference)
fweight = FIRST_WEIGHT();
else
fweight = 0;
blendclip = copyInputAttributes(context);
if(!blendclip)
num_clips = findInputClips(context, blendclip);
num_motion_tracks = findFirstAvailableTracks(context);
samples = myClip->getTrackLength();
myTotalArray.resize(samples);
myTotalArray.entries(samples);
total = myTotalArray.array();
The first part of the cook method sets up the output clip, gathers the blend clips and control tracks, and resets the total weight array to zero.
if(boss->
opStart(
"Blending Channels"))
{
for(i=0; i<num_motion_tracks; i++)
{
myClip->getTrack(i)->constant(0);
data = myClip->getTrack(i)->getData();
myTotalArray.constant(difference ? 1.0 : 0.0);
for(j=difference?1:0; j<num_clips; j++)
{
clip = getCacheInputClip(j+1);
if(!clip)
continue;
if(difference && fweight)
else
if(!track || !blend)
continue;
if (myClip->isSameRange(*clip))
{
{
if (w[k])
{
data[k] += w[k]*src[k];
total[k] += w[k]*adjust[difference];
}
}
}
else
{
{
time = myClip->getTime(k + myClip->getStart());
if (w[k])
{
data[k] += w[k] *
total[k] += w[k]*adjust[difference];
}
}
}
{
stopped = 1;
break;
}
}
For each track in the blend inputs, the input tracks are weighted by the corresponding control track and summed into the output track. When differencing, the first blend input is ignored until all the other weights can be determined.
if(!stopped)
{
if(!difference)
{
j = myAvailableTracks(i);
if ( j!=-1 && getCacheInputClip(j))
{
track = getCacheInputClip(j)->getTrack(i);
if(blend)
else
continue;
}
else
track = 0;
{
{
data[k] /= total[k];
}
else
{
if(track && blend)
{
time = myClip->getTime(k + myClip->getStart());
total[k]-= w[k];
weight = 1.0 - total[k];
total[k]+= weight;
data[k] /= total[k];
}
}
}
}
When in normal blending mode, normalize the output samples as if the summed weights added up to 1. In the case where the total weight is zero, the first blend input with that track is used to resolve the sample.
else
{
j = myAvailableTracks(i);
if (j != -1)
{
clip = getCacheInputClip(j);
if (track)
{
if (myClip->isSameRange(*clip))
{
data[k] += src[k] * total[k];
}
else
{
{
time= myClip->getTime(k+myClip->getStart());
data[k] += val * total[k];
}
}
}
}
{
stopped = 1;
break;
}
}
}
if(stopped)
break;
}
}
}
When in differencing mode, the normalization pass is slightly more complicated. The first blend input's weight is determined by the total weight; whatever weight is required to make the weights sum to 1 is what the first input is weighted with. In this case, the total was subtracted away from 1.
After the normalization pass, processing continues with the next set of blend tracks, until all have been processed.
int
{
int i, num_clips;
if(GETDIFFERENCE() && FIRST_WEIGHT())
num_clips ++;
if (num_clips >= nInputs())
num_clips = nInputs()-1;
myInputClip.resize(num_clips+1);
myInputClip.entries(num_clips+1);
for (i=0; i<=num_clips; i++)
myInputClip(i) = inputClip(i, context);
return num_clips;
}
This method puts all the blend clips into a private array called myInputClip
.
int
CHOP_Blend::findFirstAvailableTracks(
OP_Context &context)
{
int num_motion_tracks;
num_motion_tracks = 0;
for(i=1; i<nInputs(); i++)
{
clip = inputClip(i, context);
if(!clip)
continue;
}
myAvailableTracks.resize(num_motion_tracks);
myAvailableTracks.entries(num_motion_tracks);
for(i=0; i<num_motion_tracks; i++)
{
track = 0;
j = 1;
while( !track && j<nInputs())
{
clip = inputClip(j, context);
if(!clip)
{
j++;
continue;
}
if(!track)
j++;
}
myAvailableTracks(i) = track ? j: -1;
}
for(i=0; i<num_motion_tracks; i++)
{
j = myAvailableTracks(i);
if (j == -1)
continue;
track = inputClip(j, context)->getTrack(i);
if (track)
myClip->dupTrackInfo(track);
}
return num_motion_tracks;
}
This method creates output tracks from the union of all input tracks, and keeps track of the first blend input corresponding to each output track.
{
"HDK Blend",
CHOP_Blend::myConstructor,
&CHOP_Blend::myTemplatePair,
2,
9999,
&CHOP_Blend::myVariablePair));
}
This registers the HDK CHOP with Houdini. The blend CHOP has a minimum of 2 inputs, and can take an unlimited number of inputs ('9999').