Tutorial 4 : les plugins

Dans ce tutorial, nous allns apprendre à écrire un plugin pour RenderWare Graphics.

Un plugin est une extension de quelque chose qui existe déjà dans RenderWare Graphics au contraire des toolkits qui se servent de RenderWare Graphics.

Ici nous allons étendre RpAtomic pour lui ajouter un nom mais aussi pour le sauvegarder dans un fichier .DFF.

Initialisation du plugin
Ajout d'un nom

Initialisation du plugin

La première chose à faire est d'ajouter les fichiers RpAName.h et RpAName.c au projet.

Voici le premier code qu'il faut ajouter à RpAName.h

#ifndef _RPANAME_H_
#define _RPANAME_H_

#ifdef __cplusplus
extern "C"
{
#endif /*__cplusplus */
extern RwInt32 RpAtomicNameInitialize(void);

#ifdef __cplusplus
}
#endif /*__cplusplus */

#endif /*_RPANAME_H_*/

Voici maintenant le code qu'il faut ajouter pour RpAName.c.

#include <rwcore.h>
#include <rpworld.h>

#include "RpAName.h"

// ID pour le plugin
#define rwID_EXAMPLE 0xff

static RwInt32 rpExtensionOffset = -1;

//fonctions pour le plugin RpAName

//initialisation du plugin
RwInt32
RpAtomicNameInitialize(void)
{
   rpExtensionOffset =
      RpAtomicRegisterPlugin(sizeof(char *),
      MAKECHUNKID(rwVENDORID_CRITERIONTK, rwID_EXAMPLE),
      rpConstructor,
      rpDestructor,
      NULL);

   /* return the offset */
   return (rpExtensionOffset);
}

Cette fonction nous permet d'enregistrer le plugin ainsi que d'indiquer qu'il faut réserver de la place pour un char * en plus dans la structure du plugin. Pour que chaque plugin possède un numéro unique, il faut un ID de Vendor fournit par RenderWare et un ID de plugin.
On voit aussi qu'il faut une fonction rpConstructor et une fonction rpDestrcutor. Ces 2 fonctions permettent de construire et de détruite chaque Atomic créé. L'Offset obtenu par la fonction est stockée par une variable car elle sera nécessaire par le plugin.

Voici les fonctions rpConstructor et rpDestructor

/* Local functions for the atomic name plugin */
static void *
rpConstructor(void *atom,
RwInt32 offset __RWUNUSED__, RwInt32 size __RWUNUSED__)
{

   RwDebugSendMessage(rwDEBUGMESSAGE,
   "rpConstructor",
   "Atomic constructor called.");

   /* success */
   return (atom);
}

static void *
rpDestructor(void *atom,
RwInt32 offset __RWUNUSED__, RwInt32 size __RWUNUSED__)
{

   RwDebugSendMessage(rwDEBUGMESSAGE,
   "rpDestructor",
   "Atomic destructor called.");

   /* success */
   return (atom);
}

Ces 2 fonctions prennent 3 paramètres. Dans ces fonctions on fait appel à RwDebugSendMessage. Cette fonction permet d'écrire un message dans la fenêtre d'Output du debugger entre autres.

Pour que le plugin fonctionnne, il faut l'attahcer à l'application. Donc voici ce qu'il faut ajouter dans AttachPlugin après avoir inclu RpAName.h dans main.c:


if(!RpAtomicNameInitialize())
{
 return FALSE;
}

Maintenant il faut compiler en Debug et voir ce que dit le debuggeur lorsqu'on ajoute un atomic et lorsqu'on détruit l'application. On remarque que lorsqu'on appelle RpClumpStreamRead, RenderWare Graphics appelle RpAtomicCreate qui lui appelle le rpConstructor définit ci-dessus.

On va ajouter une macro pour récupere le char* à partir d'un pointer sur un atomic

/* Given an atomic, return a pointer to the extension */
#define RPEXTFROMATOMIC(a)\
   ((char **)(((RwUInt8 *)(a)) + rpExtensionOffset))

Il faut maintenant initialiser le char* contenu dans le nouvel atomic que nous avons créé. Nous allons pour cela utiliser RwMalloc et RwFree. Ici, seul RwFree apparait car ce n'est pas dans le constructeur que l'on met le nom de l'atomic.

static void *
rpConstructor(void *atom,
RwInt32 offset __RWUNUSED__, RwInt32 size __RWUNUSED__)
{

   if(rpExtensionOffset > 0)
   {
      char ** extData = RPEXTFROMATOMIC(atom);
      //l'atomic n'a pas de nom pour l'instant
      *extData=NULL;
   }

   /* success */
   return (atom);
}

static void *
rpDestructor(void *atom,
RwInt32 offset __RWUNUSED__, RwInt32 size __RWUNUSED__)
{

   if(rpExtensionOffset > 0 )
   {
      char **extData = RPEXTFROMATOMIC(atom);
      if(*extData)
      {
         RwFree(*extData);
      }
   }

   /* success */
   return (atom);
}

Ajout d'un nom

Le but de notre plugin est d'ajouter un nom à l'atomic. Pour l'instant, on peut détruire le nom mais on ne peut y accéder n'y le mettre en place. Dans cette partie on va voir comment réaliser ça.

//fonction pour mettre en place le nom de l'atomic
const char *
RpAtomicNameSetName(RpAtomic * atom,const char * name)
{
   RwInt32 len = rwstrlen(name);
   if(rpExtensionOffset>0 && len>0)
   {
      char **extData = RPEXTFROMATOMIC(atom);
      if(*extData) //s'il y a deja un nom
      {
         RwFree(*extData);
      }
      *extData = RwMalloc(len+1,rwID_NAOBJECT);
      rwstrcpy(extData,name);

      return name;
   }
   //impossible de realiser la copie du nom
   return NULL;
}

const char *
RpAtomicNameGetName(RpAtomic * atom)
{
   if(rpExtensionOffset>0)
   {
      char **extData = RPEXTFROMATOMIC(atom);
      return *extData;
   }
   //plugin non initialisé
   return NULL;
}

Il faut ensuite mettre les entêtes des fonctions dans RpAName.h

extern const char * RpAtomicNameGetName(RpAtomic * atom);

extern const char * RpAtomicNameSetName(RpAtomic * atom,const char * name);

Maintenant qu'on a crée les fonctions, il faut s'en servir... Pour cela on va afficher le nom contenu dans l'atomic sélectionné dans DisplayOnScreenInfo()

if (ShowPos)
{
   if (PickedAtomic)
   {
      RwFrame *f = RpAtomicGetFrame(PickedAtomic);
      RwMatrix *m = RwFrameGetLTM(f);

      RwChar * s = RpAtomicNameGetName(PickedAtomic);

      RsSprintf(caption, RWSTRING("<%s>:%4.2f %4.2f %4.2f"),
         s?s:"no name",m->pos.x, m->pos.y, m->pos.z);

      RsCharsetPrint( Charset, caption, 0, 2, rsPRINTPOSTOPRIGHT );
   }
}

Ainsi on peut voir que pour l'instant aucun des atomic n'a de nom.

Pour laisser le pickedAtomic persistant, il faut commenter la ligne indiquant que le PickedAtomic devient NULL.

default:
   //PickedAtomic = NULL;
   break;
}

On va définir un tableau indiquant des noms qu'on peut donner aux atomic

#define NUMNAMES 6
static const RwChar *AtomicNames[NUMNAMES] = {
   "Cube",
   "Banana",
   "Georges",
   "Toto",
   "Julie",
   "Auberon"
};
static RwInt32 SelectedName = 0;

On va définir des entrées dans le menu pour utiliser ces noms.

static RwBool
InitializeMenu(void)
{
   static RwChar fpsLabel[] = RWSTRING("FPS_F");
   static RwChar nearLabel[] = RWSTRING("Near Clip");
   static RwChar farLabel[] = RWSTRING("Far Clip");
   static RwChar showPosLabel[] = RWSTRING("Show Position");
   static RwChar namesLabel[] = RWSTRING("Name Choice_N");

   if( MenuOpen(TRUE, &ForegroundColor, &BackgroundColor) )
   {
      MenuAddEntryBool(fpsLabel, &FPSOn, NULL);

      MenuAddEntryReal(nearLabel, &NearClip, NULL, 0.1f, 10.0f, 0.1f);
      MenuAddEntryReal( farLabel, & FarClip, NULL, 10.0f, 100.0f, 1.0f);
      MenuAddEntryBool(showPosLabel, &ShowPos, NULL);

      MenuAddEntryInt(namesLabel,&SelectedName,NULL,0,NUMNAMES-1,1,AtomicNames);

      return TRUE;
   }

   return FALSE;
}

On compile et on exécute. Maintenant, il est possible de choisir le nom mais il n'est toujours pas affecté à l'atomic... On va encore ajouter une entrée au menu. Cette fois ce sera un trigger qui sera appelé (une fonction).

static RwBool
InitializeMenu(void)
{
   static RwChar fpsLabel[] = RWSTRING("FPS_F");
   static RwChar nearLabel[] = RWSTRING("Near Clip");
   static RwChar farLabel[] = RWSTRING("Far Clip");
   static RwChar showPosLabel[] = RWSTRING("Show Position");
   static RwChar namesLabel[] = RWSTRING("Name Choice_N");
   static RwChar assignLabel[] = RWSTRING("Assign Name_A");

   if( MenuOpen(TRUE, &ForegroundColor, &BackgroundColor) )
   {
      MenuAddEntryBool(fpsLabel, &FPSOn, NULL);

      MenuAddEntryReal(nearLabel, &NearClip, NULL, 0.1f, 10.0f, 0.1f);
      MenuAddEntryReal( farLabel, & FarClip, NULL, 10.0f, 100.0f, 1.0f);
      MenuAddEntryBool(showPosLabel, &ShowPos, NULL);

      MenuAddEntryInt(namesLabel,&SelectedName,NULL,0,NUMNAMES-1,1,AtomicNames);
      MenuAddEntryTrigger(assignLabel,assignNameTrigger);

      return TRUE;
   }

   return FALSE;
}

Il ne reste plus qu'à définir le trigger assignNameTrigger (dans main.c).

/**
trigger pour mettre à jour le nom de l'atomic
*/

static RwBool
assignNameTrigger(RwBool check)
{
   if (PickedAtomic && !check)
   {
      RpAtomicNameSetName(PickedAtomic, AtomicNames[SelectedName]);
   }
   return TRUE;
}

On compile, on exécute, on place des objets et on leur donne des noms!!

Nous avons vu comment nommer les objets. Nous allons voir maintenant comment lire et sauvegarder ces nouveaux paramètres!! Tutorial4 sérialisation