Tutorial - LagPoints


Overview
Since our last tutorial was an effect we shouldn't have much trouble with this one. We will be introducing a few new elements that we will spend most of our time on. If you haven't reviewed the NormScale turorial, now would be a good time.
This effect will cause an object's points to lag behind the motion of the object based on their distance from the object's pivot. A channel will be set up so that the user can animate the amount of the lag and we will allow the user of weight tools (MetaEffectors) to set influence regions on our targets. To facilitate this we will need to provide a user interface for our effect so the user can set these options.
Step 1 Create Project
Following the procedures from Concepts - Compiling, create a new project for whatever compiler you're using. Name this project "LagPoints".
Step 2 Create Files
Create the following files: LagPoints.h, PluginMain.c and LagPoints.c
Step 3 LagPoints.h
This should all be familiar, we'll explain the elements of the state data struct when we get to them in LagData.c:

#define _MESSIAH_FULL_API
#include <messiah_main.h>

// Access Function
FX_ACCESSFUNC(LagPointsAccess);

// Effect's state data structure
typedef struct
{
        FXdouble                lag;
        FXtool                  weight_obj;
        FXint                   scan;

        //work variables - used to increase speed of pointscan
        FXdouble                frame;
        FXvecd                  piv;

}LagPointState;
Step 4 PluginMain.c
This is almost identical to NormScale except that we are adding requests for a couple additional AN messages, we'll explain what they mean when we get to their handlers in LagPoints.c:

#define _MAINPLUGIN_FILE
#include "LagPoints.h"

_pluginEntry
// FXint messiahEntry(FXchar pname[256], FXvoid *api_func, FXint api, FXint program, const FXchar *rev)
{
        // Get all API components and do initial plugin setup
        _MESSIAH_PLUGIN_ALL("LagPoints");

        // Describe the effect in English
        fxModuleDescription("Points lag behind the motion of the object based on their distance from the pivot", FX_NOFLAG);

        // Request AN messages
        fxModuleAccess(ACCESS_OBJECT, O_CREATE|O_DESTROY|O_LOAD|O_SAVE, FX_NOFLAG);
        fxModuleAccess(ACCESS_SYSTEM, S_MODULE_INIT, FX_NOFLAG);
        fxModuleAccess(ACCESS_INTERFACE, IN_CREATE, FX_NOFLAG);
        fxModuleAccess(ACCESS_PROCESS, P_POST_POINT_DISPLACE, FX_NOFLAG);

        // Register the effect
        fxModuleRegister( FX_MODTYPE_EFFECT, "LagPoints", &LagPointsAccess, FX_NOFLAG);

        return FX_PLUGIN_OK;
}

_pluginExit
// FXvoid messiahExit(FXvoid)
{

}
Step 5 ACCESS_OBJECT
Except for the addition of a call to a user defined function to initialize the members of the state data struct, this handler is the same as NormScale:

case O_CREATE:
        {
                if( !(state = (LagPointState *)calloc(1, sizeof(LagPointState))) )
                        return ENTRY_CONTINUE;

                // User defined state initialization function
                LagPointDataInit(state);

                fxObjectSetTypeData( effect, state, FX_NOFLAG );

                fxChannelSetup( effect, "lag", FX_CHAN_0, FX_CHANTYPE_VALUE, state->lag, 0.0, 0.0, FX_NOFLAG);

                fxChannelDataLink( effect, FX_CHAN_0, FX_ARG_DOUBLE, &state->lag, FX_NOFLAG);

        }// O_CREATE
        break;
O_DESTROY is exactly the same, verbatim; so we won't repeat it here.
We've added in this effect the ability to load and save some information with the scene file. In the overview we said that we were going to add the ability to use weight tools, we're also adding an option so that the user can choose the way the effect will process the point data; both of those things will need to be saved with the scene. In the case of the weight tool we will need to save the object that our effect references as that tool. The processing option will be saved as an integer. The weight object and process option are stored in our state data struct as LagPointState->weight_obj and LagPointState->scan respectively. To save this data we need to set up a handler for the O_SAVE AN message:

case O_SAVE:
        {
                FX_Format *f;

                if( state = fxObjectGetTypeData( effect, FX_NOFLAG ) )
                {
                        // Create new format called "LagData"
                        f = fxFormatNew(ai, "LagData", 0);
                                
                                // Store state->weight in this format and call it "weight"
                                fxArgSaveObj(f, "weight", state->weight_obj);

                                // Store state->scan also, calling it "scan"
                                fxArgSaveInt(f, "scan", state->scan);
                }
        }//O_SAVE
        break;
An effect's state data is stored in what's called a "format", which is a block inside of a messiah file. We need to create this format before we can store any data in it, which is what we do by calling fxFormatNew() and giving it a name of "LagData". Note that we're passing ai to this function, ai is the FX_AccessInfo pointer passed to our access_func(). When we are dealing with the loading and saving of state data, ai is what will contain our formats, which in turn contain the state data. So once we've added this new format to ai we can add our data to that format. fxArgSaveObj() is used to store the state->weight_obj in the file under the name "weight". Likewise fxArgSaveInt() is used to store state->scan under "scan". If your plugin is much more complex than this one you might choose to add more than one format see ARG for info.
Loading the information back from the file is just as easy, you do this in response to the O_LOAD AN message:

case O_LOAD:
        {
                FX_Format *f;

                if( state = fxObjectGetTypeData(effect, FX_NOFLAG) )
                {
                        // initialize data prior to loading
                        LagPointDataInit(state); 
                        
                        // Retrieve our format from ai
                        f = fxFormatGet( ai, "LagData" );

                                // Retrieve state->weight_obj from the format by name (weight)
                                fxArgLoadObj(f, "weight", &state->weight_obj);

                                // Retrieve state->scan from the format by name (scan)
                                fxArgLoadInt(f, "scan", &state->scan);
                }
        }//O_LOAD
        break;
This is almost exactly the reverse process of saving the data. Instead of creating the format we get it from the ai with fxFormatGet(). Instead of saving the data we load it with fxArgLoadObj() and fxArgLoadInt(). The only other difference is that we call our initialization function for our state data before loading the data from the format. This is important because the format might not have the data saved in it (perhaps it was saved with a different version of your plugin).
Step 6 ACCESS_SYSTEM
This is the same as NormScale though you may choose to set a different color for the effect's icon with the fxInitEffectColor() function.
Step 7 ACCESS_INTERFACE
To build our GUI we will need to respond to the IN_CREATE AN message which gets sent when our interface needs to be created:

case ACCESS_INTERFACE:
        switch(entry)
        {
                case IN_CREATE:
                        {       
                                FXcontrol       block, c1, c2, c3;

                                block = fxCC_Block("LagPoint", NULL, ACC_NONE, FX_NOFLAG);

                                        c1 = fxCC_ChannelFloat("Lag", NULL, ACC_NONE, FX_CHAN_0, 0); 
                                                 fxConSetPos(c1, 75, 4);
                                        c2 = fxCC_WeightPopup("Weight", &Lag_WeightPopup, ACC_VALUE|ACC_UPDATE, 175);
                                        c3 = fxCC_Bool("Use PointScan", &LagScan_Bool, ACC_VALUE|ACC_UPDATE);

                                fxInterfaceBlockSet(FX_MODE_ANIMATE, NULL, NULL, block);
                                fxInterfaceBlockSet(FX_MODE_ANIMATE, FX_MODE_ANIMATE, FX_MBLOCK_SPLINE, FX_NULLID);
                                fxInterfaceBlockSet(FX_MODE_SETUP, NULL, NULL, block);
                        }//IN_CREATE
                        break;
        }//ACCESS_INTERFACE
        break;
Our GUI is integrated into messiah so we have to follow a few rules. The first rule is that all of our controls have to be placed in "blocks", these blocks are the collapsable parts of the interface that controls are grouped under. We will also need to set up any standard blocks that the user might expect to see, such as a Spline block for adjusting animated values.
The first thing we have done in this handler is to create the block upon which our controls will be places. This is accomplished with the fxCC_Block() function. All controls allow you to register a control_func() callback which can handle Control Notification (CN) messages. Since we will not be concerned with any messages relating to our block we leave the callback argument NULL and pass ACC_NONE as the requested CN message. Next we create and position the controls. All calls to fxCC_* functions after creating a block will place the new controls on that block. We will create a ChannelFloat using fxCC_ChannelFloat(), a WeightPopup using fxCC_WeightPopup() and a Bool (check box) using fxCC_Bool().
The channel float control needs to be linked to a channel on our effect, since we want this to control our state->lag value we'll use FX_CHAN_0 and name it "Lag". We won't be interested in messages from this control because we have already linked the channel to our state structure. The weight popup will be used to select a weight tool to be used by the effect for determining what points will be influenced by the effect and by how much. In this case we will want to be notified when the user selects a different tool from the popup so we'll need to register a control_func() and request the appropriate CN messages. We will also need to request the ACC_UPDATE CN so that when messiah needs to redraw the control we can tell it what value it should display. Our control callback looks like:

static FX_CONCALLBACK(Lag_WeightPopup)
// FXint control_func(FXcontrol ctl, FXentity ctl_data, FX_Arg *arg, FXint64 entry )
{
        FXeffect                effect = (FXeffect)ctl_data;
        LagPointState   *state = fxObjectGetTypeData(effect, FX_NOFLAG);

        if (!state) return ENTRY_CONTINUE;

        switch(entry)
        {
                case ACC_VALUE:
                        fxConGetObj(ctl, &state->weight_obj);
                        break;

                case ACC_UPDATE:
                        fxConSetObj(ctl, state->weight_obj);
                        break;

                default:
                        break;
        }
        return ENTRY_CONTINUE;
}
The effect instance that is selected when this callback is called will be passed as ctl_data, which we will cast as an FXeffect and call it effect. We'll use the effect instance to get the state data attached to it by calling fxObjectGetTypeData(). Once we've got the state data we will use the fxConGetObj() function to get the value from the control and store it in the state data when responding to the ACC_UPDATE message (which get sent when the control's value had been changed by the user). We will also use the fxConSetObj() function to set the control's value from our state data in response to the ACC_UPDATE CN (which gets sent when the control needs to redraw).
We'll do the same thing for the bool control, the only difference is that we'll be getting and setting int's instead of objects:

static FX_CONCALLBACK(LagScan_Bool)
// FXint control_func(FXcontrol ctl, FXentity ctl_data, FX_Arg *arg, FXint64 entry )
{
        FXeffect                effect = (FXeffect)ctl_data;
        LagPointState   *state = fxObjectGetTypeData(effect, FX_NOFLAG);

        if (!state) return ENTRY_CONTINUE;

        switch(entry)
        {
                case ACC_VALUE:
                        fxConGetInt(ctl, &state->scan);
                        break;

                case ACC_UPDATE:
                        fxConSetInt(ctl, state->scan);
                        break;

                default:
                        break;
        }
        return ENTRY_CONTINUE;
}
Step 8 ACCESS_PROCESS
Our P_POST_POINT_DISPLACE handler is very similar to NormScale, we will be using fxEffectTargetScan() to iterate over all of our effect's targets. We will also set the state->frame member to NOW. We do this here and store it in the state structure because we know that the frame will not change before our effect gets through calculating, and we don't want to add any overhead (NOW expands to a function call).
Our scan_func(), called EffectScan(), is where things start to get quite different. We will still be calling fxDisplaceScan() and passing it a disp_func() called DisplaceScan(), but we will add a condition that that function will only be called if state->scan is set to 1. This is all done to illustrate two different ways of displacing points and providing us with a good excuse to add a control to the interface, you'd probably never include this as an option for your users. So our scan_func() looks like this:

static FX_EFFECTSCAN(EffectScan, LagPointState, state)
{
        // ...
        fxChannelsGet(target, FX_PIVOT, state->frame, state->piv, 0);


        if (state->scan)
        {
                fxDisplaceScan(target, state->weight_obj, FX_WEIGHT_SETUP, &DisplaceScan, state, FX_NOFLAG);
        }
        else
        {
                // manually displace points
                // ...
        }

        return FX_TRUE;
}
Notice that unlike with NormScale we are providing a weight object this time to fxDisplaceScan(), it is the one stored in state->weight_obj and set by our WeightPopup. We're also indicating that the weight for each point is to be determined by the position of the weight tool relative to the point when it is in Setup mode (FX_WEIGHT_SETUP). Before calling fxDisplaceScan() though we'll get the object's pivot point and store it in state->piv. Again this is a performance issue, it's better to call fxChannelsGet once per target than once per point.
Notice the elipses in the scan_func()? There's a lot missing. If the user has chosen not to use the displace scan option (state->scan == 0) then we will have to displace each point manually here in the scan_func(). Let's Take a look at that function again:

static FX_EFFECTSCAN(EffectScan, LagPointState, state)
{
        FXvecd          pos, v, pnt;
        FXdouble        t, d;
        FXint           num_points=0, i;
        FXmatrix        m;

        fxChannelsGet(target, FX_PIVOT, state->frame, state->piv, 0);

        if (state->scan)
        {
                fxDisplaceScan(target, state->weight_obj, FX_WEIGHT_SETUP, &DisplaceScan, state, 0);
        }
        else
        {
                fxMeshNumPoints(target, &num_points, FX_NOFLAG);

                fxDisplaceBegin(target, FX_NOFLAG);

                        for (i=0;i<num_points;i++)
                        {
                                fxMeshPoint(target, i, pnt, FX_NOFLAG);

                                FX_V3SUB(v, pnt, state->piv);

                                d = FX_V3LEN(v);

                                t = state->frame - (d*state->lag);

                                fxMatrixGet(target, FX_MATRIX_WORLD, t, m, 1);

                                _fxMatrixXform(pos, v, m, 1);

                                fxDisplacePoint(target, i, pos[0], pos[1], pos[2]);
                        }

                fxDisplaceEnd(target, FX_NOFLAG);
        }

        return FX_TRUE;
}
Remember that our goal is to have the points of the mesh lag behind the motion of the object itself as a function of the point's distance from the object's pivot point and a user defined "lag factor". To displace these points manually we need to loop through all of the points in the mesh, we'll use fxMeshNumPoints() to get this information. Before we can actually start displacing points we need to make a call to fxDisplaceBegin() and pass it the object that we are going to deform.
Now for each point we've got to do some math. The first thing we need to do is get the position of the point, this is handled by calling fxMeshPoint(), passing it the object who's point we want, the id of the point and a FXvecd to store it in.
Next we need to calculate a vector from the target's pivot to the point being displaced. We'll use a macro from messiah_math.h called FX_V3SUB() which will subtract one point from another and store the value in a vector. In this case v will hold the result and pnt is subtracted from state->piv. Once we have that vector all we need to do is calculate the length of it and we have the distance of the point from the pivot (which is one of the factors in our "lag" calculation). Using FX_V3LEN() we store the length of v in d.
Now we have all the infomation we need to figure out how far forward or backward in time this point should lag. To get a frame value for this "lag amount" we'll subtract from the current frame, state->frame, the product of d and our user defined state->lag value. This will be stored in t.
In order to transform the point we need to get the world transformation matrix of the target, but we'll get the matrix from the time t rather than the present time. Calling fxMatrixGet() will accomplish this. Once done the transformation matrix will be stored in m.
We're almost there. Remember that v represented the vector from our target's pivot point to the point being displaced? Well that makes v the point represented in object space. We need to multiply v by the matrix m and we'll have our displaced position. _fxMatrixXform() will take care of that, and the new position will be stored in pos. The last thing to do is actually displace the point using fxDisplacePoint() passing the values stored in pos.
Once all of the points have been displaced we can call fxDisplaceEnd().
Todo:
finish DisplaceScan()


© 2003 pmG WorldWide, LLC.


www.projectmessiah.com

groups.yahoo.com/pmGmessiah

Last Updated on Thu Jul 10 04:49:37 2003