The plug-in animation effect is defined in the Animator as part of a costume for a ModelActor. The effect provides the capability to reposition the vertices in the model. It is requred in the Animator and in the Renderer modules. In the Animator it is required to provide a function to set the parameters that relate to the effect, and a second function to provide a preview of the effect in the Preview window. In the Renderer it must provide a function that generates the same vertex coordinate movements.
Note: The animator uses both integer and DOUBLE data types to represent the vertex coordinates, the Renderer uses both integer and FLOAT data types. In the renderer the DOUBLE data type is reassigned to FLOAT to save on memory usage.
Plug-in effects (and shaders/image processors) all have user configurable parameters. The number and type of the configurable parameters necessarily support different numbers of and different types of data. To be able to store these in the .MFX and .OFX files OpenFX stores these parameters as a text string - each effect plug-in is expected to begin by reading its parameters from the parameter text string (if it exists, or create it from defaults, if it does not exist.) In that part of the plug-in used by the Designer or Animator, before it terminates it is expected to write into the text string its parameters. This can normally be done using the C functions sscanf() and sprintf(). By adopting this strategy OpenFX imposes no constraints on the type or number of parameters.
Each animation effect is built as a Windows DLL, it will have one or more C or C++ language source file, one or more project specific header files, a Visual Studio project file, a resource file (.RC) and a file with filename extension .EFX. The project will build to a .DLL of the same name as the EFX file. The build files can have any location, but the EFX and DLL files must be placed in the "EFFECTS" folder.
The plugin source files need to include one esential header file. the "pstruct.h" header is located in the "effects" folder and it included a number of other header files defining structures used by both the Animator and the Renderer. Two of the headers define the memory allocation functions used by the current OpenFX, these are located in the "animate" folder. The "common\mem.h" file provides system speciifc memory allocation function defines.
The animation structures open to the effects to use are presented in header "sfxinfo.h" (Located in the effects folder) and the rendere structures that will be requried in an animation effect are defined in folder "render2", file ..\render2\vertex_face.h"
The resource file (.RC) will contain a dialog box description to allow the effect's parameters to be set by the user. To get a feel for a plugin animation effect we shall look at a plugin for a simple wave effect that deforms the vertices of the object it is applied two in a wave like manner.
The user loads an effect and applied it to an object using the Model Keyfram editor and adds an Effect to the object as shown here:
The files associated with the wave effect are:
these are located in the "effects" folder. The build project file is also located in the "effects" folder and there is a SLN file that includes all the effects projects.
The build process will create a DLL file called wave.dll in the effects folder. The wave.efx is a dummy file containing nothing, it is used for the external texture selection box to provide a list of all effects, it can be regarded as a simple place holder. If you want to start writing a new effect plugin then we suggest you copy the files from the example plugin to a new set. It's possible to copy the .VCPROJ files and modify it using notepad replacing all instances of the string "wave" with the name of your new texture.
This concludes all we have to say about building. Most of the other plugins in OpenFX follow a similar strategy.
The plugin code follows the basic design of a Windows DLL . In this section we will examine the parts of the source file that are common to all effects plugins. The most important items to be aware of are the names of the functions called by OpenFX's modules. These functions use pointers to data structures through which access to the OpenFX main module's (designer/animator/renderer) memory management, and other functions may be made.
OpenFX uses a pointer mechanism for allowing the plugins to access functions in the
main program and program global variables. Since all OpenFX plugins are DLLs that are
loaded into the main applications address space. The plugin DLLs are NOT loaded when the
main application starts (many of the built-in actions are also implemented as DLLs that
ARE loaded when the program starts) they are loaded when required and unloaded when they
have finished.
In the case of plugins that are used in the Designer and Animator AND are needed by the
Renderer (e.g. shaders) then the Renderer loads all the needed plugins when it reads the
script file during initialisation. The Renderer unloads the plugins when it is finished
with the script.
When the Animator requires to configure an effect it calls the function _SetExternalParameters(), with arguments:
char *_SetExternalParameterss(char *Op, ,HWND hWnd, long ruler, char *name, X__MEMORY_MANAGER *lpevi);
typedef struct tagX__MEMORY_MANAGER { void *(*fpMalloc)(long size); void (*fpFree)(void *buffer); void *lpAni; void *lpMod; } X__MEMORY_MANAGER;
The void structure members "lpAni" and "lpMod" are used to pass pointers to the internal structures ANI_STRUCTURE(Animator) and X__STRUCTURE(Designer) when appropriate! (When not used these pointers are set to NULL.)
The Designer, Animator and Renderer keep only one copy of this structure, it is initialised at start-up and passed to all the plugins. By using the members of this structure any plugin can access the memory functions used internally. This method is very simple and very flexible for extension and allowing plug-ins to access global data and functions in the calling programs.
In order that the contents of structures like this can be made available to all functions and files in a plugin the pointers passed in to the functions should normally be copied into the global variable:
Accessing variables and functions in a DLL module through direct use of a pointer to the X__MEMORY_MANAGER is very tiresome. For example to use the Designer's number of selected vertices variable (called "NvertSelect") it would be necessary to use the code ( (*(lpevi->NvertSelect)) ) to gain access to this variable. This is very messy! To overcome this issue and provide an interface to the Reindeer's functions that make them look (in the code) just like normal function calls, a number of #defines are included in the header file "defines.h" , which is one of the ".h" files that every plugin must include.
Using these defines, calls to functions and use of global variables can be used as if they were part of the module code.
Keeping this very important information in mind we can return to considering the
Checker texture plugin and look at the key code constructs.
Animation effects need to provide functions that show the action of the effect in the
Animator and render the effect in the Renderer. There is an additional complication in the
case of animation effects: Local or Global action.
To allow the effect to access the animator's internal variables two data structures are defined in the "sfxinfo.h" header. An instance of these structures is populated by the Animator and pointer sto thes are passed to the effect.
typedef float Svertex[3]; /* structure for preview vertex positions Animator*/ typedef struct { long nvert; /* number of vertices that apply to the model */ long ltime; /* integer equivalent of member "time" */ long vmin[3]; long vmax[3]; long origin[3]; float time; /* proportion of elapsed time on actor channel */ long Nfaces; void *Faces; long version; } sfxinfo; typedef struct { long size, /* size of this structure */ version, /* version of this structure */ pos[3], /* position of the object (in the scene) */ ground_pos[3], camera_pos[3], internal_axis; BOOL ground, /* 1 if ground is present in animation */ extras; float phi, theta,alpha, scale_x,scale_y,scale_z, internal_angle; } sfxdata; |
To allow the effect to access the Renderer's data structures
#define double float /* The renderer uses FLOAT real numbers */ typedef double vector[3]; #define UNSIGNED long typedef long ivector[3]; // Access is also provided to the Renderer's faces and vertices through the inclusion of the // Renderer's main data structures #inlcude "../render2/vertex_face.h" |
The required elements in a plugin effect are:
Essential headers, defines and global variables | #include <math.h> #include <windows.h> static HINSTANCE hDLLinstance=NULL; #include "pstruct.h" // essential definitions #include "paint.c" // provided funcitons to paint a window background (optional) |
DLL standard entry code for Visual Studio compiler | BOOL WINAPI DllMain(HANDLE hDLL, DWORD dwReason, LPVOID lpReserved){ switch (dwReason) { case DLL_PROCESS_ATTACH: break; case DLL_PROCESS_DETACH: break; } return (int)TRUE; } |
Function called by the Animator to allow the user to set the effect's parameters, | char * _SetExternalParameters( char *Op, /* string for the parameters */ HWND hWnd, /* parent window */ long ruler, /* ruler scale value to facilitate scaling */ char *name, /* name of DLL file with the effect */ X__MEMORY_MANAGER *lpEVI /* pointer to structure with memory functions */ ){ char buffer[128]; if(Op != NULL){ /* parameters exist so read them off the list */ /* the name of the DLL file is always the first string */ sscanf(Op,"%s % ...",buffer, ...); } if(DialogBox(hDLLinstance,MAKEINTRESOURCE( ....),hWnd, (DLGPROC)DlgProc) == FALSE)return Op; if(Op != NULL)CALL_FREE(Op); /* free the old string */ sprintf(buffer," ... ", ...); if((Op=(char *)CALL_MALLOC(strlen(buffer)+1)) == NULL){ return NULL; } strcpy(Op,buffer); // copy parameter string from local buffer to the new string. return Op; // return parameter string } |
Exported functions called by Renderer to generate the effect on the object in the scene. | long _RenderExternalEffect(char *parameters, sfxinfo *SFXinfo, vertex *v){ // Renderer function to implement the local effect in the return 1; } long _RenderGlobalEffect (char *oarameters, sfxinfo *SFXinfo, sfxdata *SFXdata, vertex *v){ // Renderer function to implement the effect if it is global. return 1; } |
Exported functions called by Animator to generate the effect in the camera view window. This will essentially be the same as the Renderer's functions. | void _PreviewExternalEffect(char *parameters, sfxinfo *SFXinfo, Svertex *v){ // modify the vertex positions for rendering in the Animator camera view } void _PreviewGlobalEffect(char *PrmList, sfxinfo *SFXinfo, sfxdata *SFXdata, Svertex *Vlist){ // modify the vertex positions for rendering in the Animator camera view return; } |
To return to the wave example, we can look at how the effect is generated. The wave effect is a local effect so only the local effect functions are used. With the exception of the format of the vertex data passed to the effect, the preview (used by the animator) and the final rendering (the effect is called by the Renderer) are exactly the same.
In the case of the preview the vertex data is a 3 element vector of floats, to set the value of vertex "i" and coordinate "j" the statement:
v[i][j] += displacement;
moves the vertex. In the case of the renderer, the statement:
v[i].p[j] += displacement;
move the vertex. (In the renderer the vertex structure is a more complex data structure that the simple list of vectors used by the animation. With this execption the code that calcuates the effect (in preview and final rendering) is basically identical so we need only examine the Preview:
// FUNCITON ENTRY void _PreviewExternalEffect(char *parameters, sfxinfo *SFXinfo, Svertex *v){ // LOCAL VARIABLES double amplitude,wavelength,displacement,PRMamp,PRMlngth,PRMfreq,d,maxd; char buffer[256]; int type, PRMaxis, axis, fade, paxis, paxis2; long i; // READ THE PARAMETERS INTO LOCAL VARIABLES sscanf(parameters,"%s %d %d %f %f %f %d",buffer,&type,&PRMaxis,&PRMamp, &PRMlngth,&PRMfreq,&fade); // CHOOSE WHICH AXIS TO WAVE AND SET IDENTIFIERS ACCORDINGLY if(PRMaxis == 3) axis=1; /* F / B */ else if(PRMaxis == 1) axis=2; /* U / D */ else if(PRMaxis == 2) axis=0; /* L / R */ if(axis == 0){ paxis = 1; paxis2 = 2; } else if(axis == 2){ paxis = 1; paxis2 = 0; } else if(axis == 1){ paxis = 0; paxis2 = 2; } // GET THE OTHER PARAMETERS AND SCALE THEM SO THAT THEY MATCH // THE OBJECTS DIMENSIONS (1 WAVE CYCLE ACROSS THE OBJECT // PICK THESE UP FROM THE OBJECT'S DATA STRUCTURE wavelength=max((SFXinfo->vmax[paxis]-SFXinfo->vmin[paxis]) * PRMlngth,1.0); amplitude=(PRMamp/25.0) * (wavelength/8); if(type == 2){ /* Radial */ maxd=0.00; for(i=0;i<SFXinfo->nvert;i++){ d=sqrt(v[i][paxis]*v[i][paxis] + v[i][paxis2]*v[i][paxis2]); maxd=max(maxd,d); } } // CALCULATE AND APPLY DISPLACEMENT TO ALL VERTICES for(i=0;i<SFXinfo->nvert;i++){ if(type == 1) d=v[i][paxis]; /* Linear */ else if(type == 2){ /* Radial */ d=sqrt(v[i][paxis]*v[i][paxis] + v[i][paxis2]*v[i][paxis2]); } displacement=amplitude*cos(PI*2*((SFXinfo->time*PRMfreq)-d/wavelength)); if(type == 1){ /* Linear */ d = v[i][paxis]-SFXinfo->vmin[paxis]; maxd = SFXinfo->vmax[paxis]-SFXinfo->vmin[paxis]; } if(fade == 2) displacement *= d / maxd; /* fade up */ else if(fade == 3) displacement *= 1 - d / maxd; /* fade down */ // UPDATE THE VERTEX COODINATES v[i][axis] += displacement; } }
This completes our discussion of the "Wave" plug-in efffect as part of the material.
Go back to the developer page...