Tutorial - NormScale


Overview
This tutorial will guide you through the steps needed to create a simple displacement plugin. This one will be a little more difficult that the last tutorial and will probably require that you do a little side reading on some topics, but it shouldn't be too bad. Once you're done with this tutorial you'll have a pretty good understanding about how effects work.
Step 1 Create Project
Following the procedures from Concepts - Compiling, create a new project for whatever compiler you're using. Name this project "NormScale".
Step 2 Create Files
Unlike our last tutorial, this one will be a bit larger so it would be a good idea to split it up into a couple of files. Create the following files: NormScale.h, PluginMain.c and NormScale.c
Step 3 NormScale.h
We'll include the whole API for the hell of it:

#define _MESSIAH_FULL_API
#include <messiah_main.h>
Notice that we've left out _MAINPLUGIN_FILE before including messiah_main.h. We only defined that along with _MESSIAH_FULL_API in the previous tutorial because we put everything in one file, since we're splitting things up here we'll hold off on defining that symbol until we get to PluginMain.c. The next thing we'll need to do in this header is declare our module's Access Notification message handler, we'll call it NormScaleAccess:

FX_ACCESSFUNC(NormScaleAccess);
The last thing we'll do in this file is create a structure that will hold the state data of our effect instances. We'll discuss this structure more later, but for now just add it to this header so that you end up with:

#define _MESSIAH_FULL_API
#include <messiah_main.h>

FX_ACCESSFUNC(NormScaleAccess);

typedef struct
{
        FXdouble        scale;
} NormScaleState;
Step 4 PluginMain.c
We will need to include NormScale.h in this file, but first we must define _MAINPLUGIN_FILE. Next we'll start the registration process, filling in a few gaps as we go:

_pluginEntry
{
        // Get all API components and do initial plugin setup
        _MESSIAH_PLUGIN_ALL("Norm_Scale");

        // Describe the effect in English
        fxModuleDescription("Scale a mesh's points along their normals", FX_NOFLAG);

        // Register the effect
        fxModuleRegister( FX_MODTYPE_EFFECT, "NormScale", &NormScaleAccess, FX_NOFLAG);

        return FX_PLUGIN_OK;
}
With only a few minor changes this is almost exactly what we had for our first Function Module. Notice that instead of fxFunction*() functions we are using fxModule*() instead (fxModuleDescription(), fxModuleRegister()). See Modules for an explanation about this change.
The only thing we'll need to add to our registration process is a request for Access Notification messages:

// Request AN messages
fxModuleAccess(ACCESS_OBJECT, O_CREATE|O_DESTROY, FX_NOFLAG);
fxModuleAccess(ACCESS_SYSTEM, S_MODULE_INIT, FX_NOFLAG);
fxModuleAccess(ACCESS_PROCESS, P_POST_POINT_DISPLACE, FX_NOFLAG);
The first request tells messiah that we want to be notified whenever an instance of our effect is created or destroyed. The second tells it that we want to be notified when our module is being initialized (that is the module class, not an instance). Finally we are telling messiah that we want to be notified when it wants us to calculate our point displacement. These requests must be made prior to registering the effect just like the effect's description. What we end up with for the whole PluginMain.c is:

#define _MAINPLUGIN_FILE
#include "NormScale.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("Norm_Scale");

        // Describe the effect in English
        fxModuleDescription("Scale a mesh's points along their normals", FX_NOFLAG);

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

        // Register the effect
        fxModuleRegister( FX_MODTYPE_EFFECT, "NormScale", &NormScaleAccess, FX_NOFLAG);

        return FX_PLUGIN_OK;
}

_pluginExit
// FXvoid messiahExit(FXvoid)
{

}
Step 5 ACCESS_OBJECT
In our AN handler, NormScaleAccess, we will first deal with the ACCESS_OBJECT Access Level. This will go in our NormScale.c file, here's the framework:

#include "NormScale.h"

FX_ACCESSFUNC(NormScaleAccess)
// FXint f(FX_AccessInfo *ai, FXentity ID, FXint level, FXint64 entry)
{
        switch( level )
        {

        case ACCESS_OBJECT:
                switch( entry )
                {
                case O_CREATE:
                        {

                        }// O_CREATE
                        break;

                case O_DESTROY:
                        {

                        }// O_DESTROY
                        break;
                } // switch entry

        }// switch level

        return ENTRY_OK;
}
If that looks a little scary you might want to take a few minutes to review:Access Notification.
The first of the two AN messages that we'll handle for the ACCESS_OBJECT AL will be O_CREATE. This message will be sent whenever an instance of our effect is created. Since this same function will get called for all instances of our effect we will need to be able to identify which effect is being referred to by the message being handled. We'll create an FXeffect variable that has function scope and cast the ID argument to that variable:

FX_ACCESSFUNC(NormScaleAccess)
{
        FXeffect        effect = (FXeffect)ID;
        //...
}
This is necessary because Access Functions are not limited to effects only, but ID will always be the instance that the message is regarding.
The first thing we'll want to do when handling O_CREATE is to allocate our state data struct and attach it to our effect. The state data struct is what will allow us to store any information we choose with each instance of our effect. In this case we will want to store a value that represents the amount each point should be scaled along it's normal. Since we'll use a pointer to this data elsewhere in our NormScaleAccess() function we'll create a pointer with function scope called state:
FX_ACCESSFUNC(NormScaleAccess)
{
        FXeffect        effect = (FXeffect)ID;
        NormScaleState * state = NULL;
        //...
}
Now that we've got a pointer, let's allocate storage for our state struct and attach it to our effect(fxObjectSetTypeData()):

if( !(state = (NormScaleState *)calloc(1, sizeof(NormScaleState))) )
        return ENTRY_CONTINUE;

fxObjectSetTypeData( effect, state, FX_NOFLAG );
We will need to be sure to free this memory when it's no longer needed, we'll do this shortly when we handle O_DESTROY.
The last thing to do in O_CREATE is to set up our channels so that the user can animate the scaling factor. We'll use our effect's first channel, FX_CHAN_0, and call it "factor". We will also link that channel to the scale member of our NormScaleState structure:

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

fxChannelDataLink( effect, FX_CHAN_0, FX_ARG_DOUBLE, &state->scale, FX_NOFLAG);
The call to fxChannelSetup() changes the name of the first channel to "factor" and initializes it's value to that of state->scale. The call to fxChannelDataLink() links the first channel to state->scale so that whenever the value of the first channel changes ( user alters the value, time changes etc. ) our state data will be updated automatically for us so that we do not need to query the value of this channel when we process our effect.
When handling O_DESTROY we'll only need to get rid of the state data, if any exists:

case O_DESTROY:
        {
                if( state = fxObjectGetTypeData( effect, FX_NOFLAG ) )
                {
                        free(state);
                        fxObjectSetTypeData( effect, NULL, FX_NOFLAG);
                }
        }// O_DESTROY
        break;
ACCESS_SYSTEM
We're only handling one AN message from the ACCESS_SYSTEM AL, S_MODULE_INIT. This message is sent when our module itself is being initialized as opposed to when an instance of our module (effect) is being created. In response to this notification we will want to provide a little information about our effect, namely what Tool the effect should use (fxInitEffectTool()), how it will be displayed in messiah (fxInitEffectManipulator()) and what color we want it's icon to be(fxInitEffectColor()):

case S_MODULE_INIT:
        {
                fxInitEffectManipulator(FX_MANIPULATOR_SLIDER, FX_NOFLAG);

                fxInitEffectColor((FXubyte)200, (FXubyte)220, (FXubyte)0);

                fxInitEffectTool(FX_TOOLTYPE_NULL, FX_NOFLAG);
                
        }// S_MODULE_INIT
        break;
ACCESS_PROCESS
We'll handle the P_POST_POINT_DISPLACE AN message in this AL. This is where we'll actually deform the attached target(s). Because there might be more than one target for this effect we will need to process them all. To make this easy the messiahAPI provides a feature called Target Scanning (fxEffectTargetScan()) which allows you to register a callback that will get called once for each target attached to an effect, thus you do not need to manually iterate over the effect's targets:

case ACCESS_PROCESS:
        switch( entry )
        {
        case P_POST_POINT_DISPLACE:
                {       
                        if( state = fxObjectGetTypeData( effect, FX_NOFLAG ) )
                        {
                                // Process each target, EffectScan() is called once for each attached target
                                fxEffectTargetScan( effect, &EffectScan, state, FX_NOFLAG );
                        }
                }// P_POST_POINT_DISPLACE
                break;
        }// ACCESS_PROCESS
        break;

We'll create a function called EffectScan() using the FX_EFFECTSCAN() macro, from there we will call another scanning function that will iterate over all of the points in that target (fxDisplaceScan()). That second callback, which we'll call DisplaceScan(), is declared and defined with FX_DISPLACESCAN():

static FX_DISPLACESCAN(DisplaceScan, NormScaleState, state)
//      FXint f(FX_DisplacePoint *p, dt *ed)
{
        //...
        return FX_TRUE;
}

static FX_EFFECTSCAN(EffectScan, NormScaleState, state)
//      FXint f(FXeffect effect, FXtool tool, FXobject target, dt *ed)
{
        // Process each point in target, DisplaceScan() gets called once for each point
        fxDisplaceScan( target, FX_NULLID, FX_NOFLAG, &DisplaceScan, state, FX_NOFLAG );

        return FX_TRUE;
}
Just to recap that in case it's a little unclear. When our effect needs to displace the items it affects, it's targets, messiah will send a P_POST_POINT_DISPLACE AN message to us. In response to that we will call the fxEffectTargetScan() function, passing it the address of a function we created (EffectScan) that messiah will in turn call once for each of the effect's targets. That function will then call fxDisplaceScan() passing it the address of another function we created (DisplaceScan) that messiah will call once for each point in the target. So the place that the actual displacement occurs is in our DisplaceScan() function.
So filling in DisplaceScan we have:

static FX_DISPLACESCAN(DisplaceScan, NormScaleState, state)
//      FXint f(FX_DisplacePoint *p, dt *ed)
{
        FXdouble norm[3];

        fxMeshPointNormal( p->objID, p->ID, norm, FX_NOFLAG );

        p->new_pos[0] = p->cur_pos[0] + (state->scale * norm[0]);
        p->new_pos[1] = p->cur_pos[1] + (state->scale * norm[1]);
        p->new_pos[2] = p->cur_pos[2] + (state->scale * norm[2]);

        return FX_TRUE;
}
This is about as simple a displacement as you can get. The first thing we do is get the surface normal of the point being displaced. To do this we call fxMeshPointNormal() using the information passed to our callback in the FX_DisplacePoint * p. The object the point belongs to is indicated by the objID member and the index of the point being displaced is indicated by the ID member. The normal is then stored in our local variable norm. We then set the p->new_pos to the value of our displaced point. We're adding the displacement value to p->cur_pos so that our effect will work with any effects that already deformed this target object. If we didn't add this value our effect would override any prior effects (i.e. ones that appear hight in the list of effects in messiah's Setup->Effects list).
Summary
So that's an effect, that wasn't too bad was it? In the next tutorial we'll create another effect that has some added features such as an interface, ability to use weight tools and load and save information to a scene file.


© 2003 pmG WorldWide, LLC.


www.projectmessiah.com

groups.yahoo.com/pmGmessiah

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