Developing an Animation Preset plug-in for OpenFX

Introduction

The Animation module offers developers the potential to augment the New from Template command by adding to the preset templates provided with OpenFX for creating standard types of animations, or animations for special purposes (take a look at the Domino preset for an example of this). From the developer's point of view this is done using the same approach as all the other plugins for OpenFX.

Building and Files

Each Animator template plugin 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 .PFX.  The project will build to a .DLL of the same name as the PFX file. The build files can have any location, but the PFX and DLL files must be placed in the "PRESETS" folder.

The plugin source files need to include two header files that are located in the "ANIMATE" folder.  These are "astruct.h" and "dstruct.h" . These define the Animator's data structures and provide prototypes for the Animator's internal functions that can be used from within the template plugin, they also give access to the Animator's key data structures. The Animator's functions that the developer can use are not defined in a library, they are accessed through a pointer that is passed to the plugin at run time. However, they can be used in the plugin as if they were ordinary functions. There is no need to link any of the plugins with a special library.

In the case of an Actions plugin, the resource file (.RC) will usually contain a dialog box description to allow the user to set up the action.  There is also a .DEF file that defines the functions to be exported for use by the "New From Template" command, this DEF file is common to all presets and indicates that the exported function is called "_ExternalPreset()".  
When the user selects the "New From Template" command, OpenFX builds a selection box of templates on offer by adding to the list of internal templates any templates existing in the Presets folder. It does this by finding all the .PFX files in the Presets folder, and adding the text in them to the list. Thus, the .PFX files contain a short text string identifying the action of the preset. If the user selects an external preset then OpenFX finds the DLL with the same name as the chosen PFX file, loads it into memory, calls function _ExternalPreset() to configure the animation. _ExternalPreset() might do things like set the duration of the animation,  ask the user to select one or more .MFX models to appear in the animation, arrange cameras and lighting etc. etc. Almost any action can be set up with a preset template. (This is in effect a form of animation scripting language - but one easily used by anyone who knows C or C++ programming.)

To get a feel for a plugin action we shall look at the 3DVIEW template, this template arranges for a user selected model to spin before the camera so that it can be seen from every angle. The resulting sequence of frames once rendered, can be used by one of the utilities to provide an interactive view of the object.

The files associated with the 3DVIEW template are:

located in the presets folder.

The build project file is also located in the "preset" folder (and all preset projects are part of the presets SLN file.

The build process will create a DLL file called 3dview.dll in the preset folder.  As mentioned above the 3dview.pfx  file contains the text that will appear in the New from Template command's selection box. In the case of our example  the file contains the text: "Create-Spanning-View"  so this is text that will appear in the selection. For this particular template there is a very simple user interface dialog that give information and asks the user to select the .MFX model to be viewed.

Building is done in Visual Studio  If you want to start writing a new preset  plugin then we suggest you copy the files from one of the existing ones  to a new set (with the same filename extensions). Modify the .VCPROJ files using a simple text editor to reflect the name change and then add that to the preset solution. (or set up an individual solution to contain it.)  

This concludes all we have to say about building. Most of the other plugins in OpenFX follow a similar strategy.

The plugin-code

The plugin code follows the basic design of a Windows DLL . In this section we will examine the parts of the preset source file that are common to all presets. The most important items to be aware of are the name of the function called by OpenFX's Animation  module to execute the effect called _ExternalPreset(...), the include files that define the OpenFX Animator's  data structures "astruct.h" and the external function definition and global variable definition header file "dstruct.h" (Note: do not get the dstruct.h file use by the Animator and the dstruct.h file used by the Designer confused the first is located in the "animate" folder, the second in the "design" folder. The first is used by Animator plugins, the second by Designer plugins."

The OpenFX method of accessing global variables and main program functions   (VERY IMPORTANT)

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 application's address space, all global functions and variables can be accessed by any loaded DLL, it is only necessary to know where they reside in memory,   OpenFX does this by providing a collection of  C/C++ pointers. 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 the user is finished using them..
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 an external action to be performed it calls  the function _ExternalPreset(..), with two arguments:

BOOL _ExternalPreset(HWND parent, ANI_STRUCTURE *lpevi)
typedef struct tagANI_STRUCTURE {
 ..
 short *Nnodes,*Nground,*Nrobots,
       *Nskys,*Ncameras;
 struct NODE **MainNp,**FirstNp,**SelectedCamera,**SelectedNode;
 ..
 long   *Nframes,*CurrentFrame;
 short (*fpGetTransform)(short,
             long, double,
             struct NODE *,  struct OBJECT *,
             point, point,
             double *, double *, double *, double *, double *,double *,
             short *, double *);
 void (*fp_scal)(double [4][4], double, double, double);
 struct OBJECT * (*fpCreateActor)(struct NODE *, long, long);
 struct NODE * (*fpCreateNode)(void);
 ..
 void *dummyf[64];
} ANI_STRUCTURE;

Full details of the members of this structure are available in the code documentation.  The global variables and functions referenced in this structure allow the developer  to programmatically generate animations of any complexity and length. Some of the variables and function arguments refer to other OpenFX specific data types but definitions for these, as well as many useful constants are included in the "struct.h" file.

The Animator  keeps only one copy of this structure, it is initialised at start-up and passed to the preset plugin.  By using the members of this structure any plugin can access the functions used internally by the animator.  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 ANI_STRUCTURE (or the X__MODELLER structure used by the Designer 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 functions that make them look (in the code) just like normal function calls, a number of #defines are included in the header files: "define.h" for the designer "dstruct.h" for the animator and there are other equivalent one for the shaders, effects, and post processors.

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 3D view preset template and look at the key code constructs.

Essential headers and global variables
#include <windows.h>
static HINSTANCE hDLLinstance=NULL; /* use to pick up resources from DLL   */
#include "..\animate\astruct.h"
#include "..\animate\dstruct.h"
DLL standard entry code for Visual Studio compiler
BOOL WINAPI DllMain(HANDLE hDLL, DWORD dwReason, LPVOID lpReserved){
  switch (dwReason) {
    case DLL_PROCESS_ATTACH:
      hDLLinstance = hDLL;  /* handle to DLL file */
      break;
    case DLL_PROCESS_DETACH:
      break;
  }
return (int)TRUE;
}
Exported function to implement the action.
BOOL _ExternalPreset(HWND parent, ANI_STRUCTURE *lpevi){
 // Put actions code here
 return TRUE;
}

The 3D view example

To return to the 3D view example in this section we just look at the key steps in the code and how they use the Animator's internal functions. We will do this by using a heavily annotated version of the code for the _ExternalPreset(..) function.  In creating an Animation the code must specify the number of frames. We can think of the specification of an animation as a tree like graph, the root of the graph represents the animation. From the root a number of NODES branch out, these are the Actors (Cameras, models, Sky, Ground etc.)  When the Animator is reset to a "New Animation" one NODE (Actor) is always created, it is a Camera  Other actors can be added to the scene by creating additional NODES. The nodes are stored as a doubly linked list of NODE data structures. The first node in the list is identified with a global pointer "FirstNp", the number of Nodes in  the list  is given by the global variable "Nnodes".  having access to just these two variables one can get access to the Animator's entire data . Three are some auxiliary variables that are useful, for example the number of camera, the number of Sky and Ground actors (only one of each is allowed. All these variables can accessed through the members of the ANI_STRUCTURE.

New nodes are NOT created directly, they are created using Animator functions that manipulate the linked lists.  Each node can represent any time of actor, each actor manifests itself through an entry in the keyframer (the keyframer's can be thought of as a view of the "tree graph" representing the animation.)   To perform an animation the actors require to know:

Within the NODE structure for each actor there are pointers to doubly linked lists for each of  4 types of timelines that can be used by each actor. Each entry in the double linked lists of timelines represents the segments of a timeline with its keyframe at the end. The timeline segment specifies the range of frames over which that actor will, for example, stand at location X.  (If there are two or more keyframes for a specific actor then that action can be "tweened" during the intervening frames.)

The Node data structure members provide pointers to the head of the lists for the timelines.  The Sky and Director actors require special costume properties and these costume timeline segment lists are separated from the general actor costume type that is represented by the OBJECT data type. Actors types are identified by an integer:

#define NORMAL      0
#define PATH        1
#define TARGET      2
#define GROUND      3
#define LIGHT       4
#define ANIMOBJ     5
#define SKY         6
#define CAMERA      7
#define PARTICLE    8
#define ROBOT       9
#define IMAGEP     10
#define DIRECTOR   11

In addition to a pointer to the SSKY , DIRECTOR and OBJECT lists for this actor the NODE structure contains pointers to the first entry in the lists for the position, size and alignment timelines for that particular Actor.

The OBJECT linked list  is the most complex list, as it represents the costume for models,  paths, cameras, robots, effects and particle systems.

With a knowledge of the data structure used by the Animator we can no proceed to examine the code used to generate the 3Dview preset, try to follow the comments for examples of how to create actors, keyframes and timeline segments, set positions etc.

:

BOOL _ExternalPreset(HWND parent, ANI_STRUCTURE *lpevi){
 // LOCAL VARIABLE DECLARATION
 object   *Op;
 position *Pp,*Pc,*Pl;
 align    *Ap;
 char f_name[256],f_dir[256];
 int i,j,k,nfi,nalpha,nf;
 double fi,alpha,dfi,dalpha;
 long xmax,ymax,zmax,size,xmin,ymin,zmin;

 // MAKE COOPY OF STRUCTURE POINTER INTO VARIABLE DECLARED IN FILE DSRUCT.H
 lpEVI=lpevi;

 // PROVIDE THE USER WITH SOME INFORMATION
 LoadString(hDLLinstance,IDS_TITLE,f_name,256);
 MessageBox(parent,f_name,"3D View",MB_OK);
 // INITIALISE VARIABLES NEEDED TO CALCULATE THE VIEWING ANGLES
 nfi=18;
 nalpha=11;
 nf=nfi*nalpha;
 fi = -180.0; alpha = -75.0;
 dfi=fabs(2.0*fi/(double)nfi);
 dalpha=2.0*fabs(alpha/(double)nalpha);
 // CALL ANIMATION FUNCTION TO SET THE DURATION OF THE ANIMATION
 SetNumberOfFrames((short)nf);

 /* add Camera position channel and move the camera - Camera always present */
 if(CreatePosition(FirstNp,1,(short)Nframes) == NULL)return FALSE;
 // GET POINTER TO THE FIRST ACTOR'S (THE CAMERA) POSITION TIMELINE 
 Pc=FirstNp->fpos;

 // SET THE POSITION OF THE CAMERA - BASE THIS ON THE CURRENT SIZE OF THE VIEW
 // WINDOWS (CALLED THE TriView AND DEFINED IN GLOBAL VARIABLES TVxxxx etc.
 FirstNp->fpos->finish[0]=0;
 FirstNp->fpos->finish[1]=TVpointY+TVsizeY*2;
 FirstNp->fpos->finish[2]=0;

 /* create a Target Actor and create a Costume for it over the whole 
    animation duration - default position is at (0,0,0) */
 if(CreateNode() == NULL)return FALSE;
 strcpy(MainNp->actorname,"Target");
 MainNp->type=TARGET;
 if((Op=CreateActor(MainNp,1,(short)Nframes)) == NULL)return FALSE;
 Op->type=MainNp->type;
 /* Now point the Camera to Look at the target */
 // CREATE A TIMELINE AND KEYFRAME ASSIGN IT TO LOOK AT THE TARGET 
 if(CreateAlign(FirstNp,1,(short)Nframes) == NULL)return FALSE;
 FirstNp->fali->type=TRACK; FirstNp->fali->topath=MainNp;

 /* Now create a light actor */
 if(CreateNode() == NULL)return FALSE;
 strcpy(MainNp->actorname,"Light");
 MainNp->type=LIGHT;
 // CREATE A COSTUME FOR THE LIGHT
 if((Op=CreateActor(MainNp,1,Nframes)) == NULL)return FALSE;
 Op->type=MainNp->type;
 // GIVE IT PROPERTIES
 Op->lighttype=SPHERE; Op->colour[0]=Op->colour[1]=Op->colour[2]=255;
 /* Create Position Channel (timeline segment) for Light */
 // OVER THE FULL ANIMATION DURATION (frames 1 to Nframes
 if((Pp=CreatePosition(MainNp,1,(short)Nframes)) == NULL)return FALSE;
 Pl=Pp;
 Pp->finish[0]=0;
 Pp->finish[1]=FirstNp->fpos->finish[1]+TVsizeY;
 Pp->finish[2]=0;


 /* Add Actor with Chosen Model for Costume */
 if(CreateNode() == NULL)return FALSE;
 strcpy(MainNp->actorname,"Actor");
 MainNp->type=NORMAL;
 if((Op=CreateActor(MainNp,1,(short)Nframes)) == NULL)return FALSE;
 Op->type=MainNp->type;
 // USE GLOBAL VARIABLES TO IDENTIFY OPENFX's FOLDER LOCATION
 // AND BUILT IN FUNCTION TO SELECT THE MFX MODEL FILE
 strcpy(f_dir,gszHomeDir); strcat(f_dir,"objects"); strcpy(f_name,"*.mfx");
 if(SelectFileName(0,f_name,f_dir,"Choose Model To View",
                   "(*.MFX)|*.mfx|",parent) == TRUE){
   // FILENAME IS OK 
   strcpy(Op->name,f_name);
   Op->morph=NO;
   // TELL OPENFX TO LOAD THE MODEL FROM THE CHOSEN FILE _ IF IT FAILS
   // DELETE THE ACTOR AS IT WILL BE OF NO USE IN THE SCENE
   if(StageLoadObject(f_name,Op,YES,YES,NO) == FAIL)DeleteActor(MainNp,1);
 }
 else return FALSE;

 /* Move the camera so that it shows object - do this by determining the 
    the extent of the Object we have just loaded from the MFX file    */
 if(Op->npoints > 0 && Op->points != NULL){
   xmax=ymax=zmax=size= -MAXUNIT;
   xmin=ymin=zmin=MAXUNIT;
   for(i=0;i<Op->npoints;i++){
     if(Op->points[i][0]  > xmax)xmax=Op->points[i][0];
     if(Op->points[i][1]  > ymax)ymax=Op->points[i][1];
     if(Op->points[i][2]  > zmax)zmax=Op->points[i][2];
     if(Op->points[i][0]  < xmin)xmin=Op->points[i][0];
     if(Op->points[i][1]  < ymin)ymin=Op->points[i][1];
     if(Op->points[i][2]  < zmin)zmin=Op->points[i][2];
   }
   size=max(xmax-xmin,ymax-ymin);
   size=max(size,zmax-zmin);
   Pc->finish[1]=TVpointY+max(size*3,TVsizeY*2);
   Pl->finish[1]=Pc->finish[1]+TVsizeY*2;
 }
 // NOW CREATE A SET OF ALIGNMENT KEYFRAMES (and timeline segments) FOR THE OBJECT WE HAVE
 // JUST LOADED SO THAT IT TAKES UP AN ORIENTATION THAT MAKES SURE ALL PARTS OF IT
 // WILL BE VISIBLE TO THE CAMERA AT SOME POINT IN THE ANIMATION
 k=1;
 for(j=0;j<nalpha;j++){
   // CREATE THE ALIGNMENT KEYFRAME AND TIMELINE SEGMENT 
   if((Ap=CreateAlign(MainNp,k,k+nfi-1)) == NULL)return FALSE;
   // SET THE ALIGNMENT SO THAT IT INTERPOLATES FROM THE PREVIOUS KEYFRAME VALUE
   Ap->type=TWEEN;
   Ap->im=1; Ap->ima=1.0;
   Ap->alpha=(alpha+(double)j*dalpha);
   k += nfi;
 }
 return TRUE;
}

To get more information about the structure member definitions and global variables used in the above examples consult the OpenFX source code and source code DOXYGEN documentation.

This completes our discussion of the 3Dvew example. You should look at the code for the other plugins, find and identify the similarities with this example and follow the logic of the argument. .

Go back to the developer page...