3DSMax 3 : bidouille sur les données

ChrisbK ( chrisbk@ifrance.com )


Youpla tous. Chaipas si jamais vous avez déja voulu exporter des trucs de MAX, mais si c'est le cas zavez ptet deja vu que parfois, on est un peu perdu ... Donc le but de cette doc est de faire une sorte de "how to export/bricole" . Bon, ca fait beaucoup pour un seul homme, alors si jamais vous voulez rajouter (corriger, aussi, dans la foulée) des infos, hésitez pas. Pour le moment y'a pas trop grand chose, mais bon, ca doit faire partie des choses qui vont en s'améliorant avec le temps...

Allez, zou



Introduction et Rappels
Consulter les SuperClassID et les class ID
Stocker des infos dans les INodes
Exemple d'export : systeme de particules
Exemple d'export : Space Warp
Retrouver le lien entre un Space Warp et un système de particule



Rappels et introduction


Une des classes pilier de max est la class Animatable . D'elle dérive tout un tas de truc et de machin, dont en particulier la class INode. Les INodes étant, comme leur nom le laisse penser, des noeuds . Chacun de ces noeuds réference un objet . (pour avoir l'objet réferéncé par un noeud, un majesteux node->GetObjectRef() et c'est bon) .
Ensuite tous les noeuds sont stockés dans une sorte de liste chainée, dont le premier élement est accessible grâce à la classe Interface (MAX vous refile un pointeur vers un objet de cette classe) .
Etant donnée que cette classe comporte beaucoup de fonction , je vous conseille d'aller faire un un tour dans l'aide du SDK de MAX. Par exemple, pour scanner tous les nodes :

void scanNode(INode *node)
{
	// magouille sur le node à insérer ici
	for (int c = 0; c < node->NumberOfChildren(); c++) 
		scanNode(node->GetChildNode(c));
}

scanNode(ip->GetRootNode());	//IP est un pointeur vers un Interface, fourni par MAX



Chacun des objets (et meme tous les plug in) est identifié par deux ID : une SuperClassId, qui définie la "famille" de l'objet (geom object...) et la classID, qui elle définie donc précisement l'objet . La classID, c'est deux unsigned long , qui doivent être unique. Y'a un prog fourni avec max pour en générer automatiquement ( si vous refaites un plug in à partir des examples fournis avec le SDK, pensez en premier à changer la Class_ID)

Quelques fonction utiles sur les node


TSTR node->GetName(); nom du node
Mtl * node->GetMtl(); materiau du node
ObjectState node->evalWorldState(int t)renvoie un objectState. celui ci contient un pointeur vers un objet, objet qui est la représentation de l'objet au moment t. Très pratique, pour l'export de mesh par exemple.
Matrix node->GetNodeTM(int time);renvoie la matrice de transformation du node au temps t




Consulter les SuperClassID et les class ID

Pour une SuperClassID :
ObjectState os = node->EvalWorldState(0);
if (os.obj) os.obj->SuperClassID(); //Renvoie la SClassId
Et pour la classId
ObjectState os = node->EvalWorldState(0);
if (os.obj) os.obj->ClassID(); //idem que précedemment, sauf que cette fois c'est la classId (formidable, non ?)

Ces fonctions nous seront plus qu'utile par la suite pour savoir sur quel type d'objet nous sommes.




Stocker des données dans un node


Il y a principalement (et à ma connaissance) deux façons de stocker des infos dans un node (userPropertie et AppDataChunk).

1. user Properties

La première, la plus facile (non pas que l'autre soit difficile), c'est les userProperties .
Zavez ptet deja vu, quand vous faite proprieté d'un objet puis user defined . Ben c'est ca, les users properties . Du texte.

Une proprieté aura cette tête la :
clé = valeur
où "clé" est une chaine de caractère (qui ne doit pas contenir d'espace) , "valeur" un int,float ou une chaine de caractère
Prenons un node et regardons si il a la clé Pouet, et si oui, stockons la valeur de la clé dans la variable pouet:

TSTR valPouet;
node->GetUserPropString("POUET",valPouet);
ou bien :

int valPouet;
node->GetUserPropInt("POUET",valPouet);
voir meme :

float valPouet;
node->GetUserPropFloat("POUET",valPouet);
Voyez, ca casse pas trois pates à un canard . La même chose existe pour les Sets (SetUserPropFloat...)

Le soucis, c'est que bon, c'est de l'ascii, et que n'importe qui peut venir avec vos gros sabots tout sacager . (et si c'est un identifiant achement important ou que sais-je, ca peut être gênant)
Donc, pour ces cas de figure, nous avons les AppDataChunks (tadaaa!)



2. AppDataChunk


Qu'est ce qu'un AppDataChunk ? tout simplement un bout de mémoire (alloué par vos soins) associé au node . No more, no less.
La aussi, inutile d'avoir un gros cerveau pour faire marcher l'affaire (ca marche avec n'importe quel objet dérivant de animatable, c'est à dire beaucoup)

Au hasard, je stocke une donnée dans un Materiau
Mtl *mat = node->GetMtl();
if (mat)
	mat->AddAppDataChunk(id,Sid,subChunkId,size, (void *)monPointeurVersMesDonnees);
id, c'est la classeId du propriétaire . (classID de votre plug in, par exemple)
Sid, c'est la superClassId du proprio . (SuperClass ID de votre plug in)
SubChunkId, c'est pour vous ca, si vous voulez organiser vos données un tantinet . vous mettez ce que bon vous semble.
Size c'est tout simplement la taille de ce qui est pointé par "monPointeurVersMesDonnees".

Pour faire machine arrière :
AppDataChunk * appData = mat->GetAppDataChunk(id,sid,subChunkId);
si appData est different de NULL, alors c'est que les données ont été trouvées, et vous pouvez y accèder via appData->data.

A Noter : la doc de Max précise que la mémoire que vous fournissez via monPointeurVersMesDonnees doit avoir été allouée avec "malloc" (vu qu'elles sont ensuite désallouées via free()).

Warning : Hum, j'ai eu il y a peu un petit soucis (mais bien chiant), j'avais eu la sale idée de ne stocker qu'un octet dans mon AppDataChunk . Ben ca marchait pas du tout . J'ai passé la taille à 2 octets et c'etait tout de suite OK ... Si quelqu'un à une idée la dessus ...





Sauvegarde des AppDataChunk & userPropeties

Rien a faire, max le fait pour vous . Un vrai St Bernard.






Export d'un système de particule

Prenons ici comme exemple le système de particule de type snow , tout simplement parce que c'est le plus simple:) et pour les autres, c'est grosso modo pareil.

Les systèmes de particules ont comme SClassID "GEOMOBJECT_CLASS_ID" . Donc si on part d'un Node, on fait :
ObjectState os = node->EvalWorldState(t);
if (!os.obj || os.obj->SuperClassID()!=GEOMOBJECT_CLASS_ID) {
	return;
}

Bien, maintenant, voyons si notre noeud est bien un système de particule "snow" .


if (os.obj->ClassID() == Class_ID(SNOW_CLASS_ID,0))
{
	//pas de doute, on a bien un systeme de particule
	//de type snow
}
Dernière étape (dans le cadre d'un plug in d'export, ce qui etait mon but), trouver les données qui nous interésse . Elles sont stockées dans un paramBlock, contenu lui dans un SimpleParticle du plus bel effet
//on recup l'objet référencé par le Node, et on le recast direct
//en SimpleParticle (on fait ca car on est sur d'etre sur un "Snow" grâce aux ID)

SimpleParticle *part = (SimpleParticle *)node->GetObjectRef() ;

//Récup des données par le paramBlock


float speed     ;
float variation	;

part->pblock->GetValue(PB_SPEED,0,speed,FOREVER);
part->pblock->GetValue(PB_VARIATION,0,variation,FOREVER);
etc ...

Vous pouvez trouver les PB_SPEED & autre dans le fichier RAIN.CPP (rep Sample/objects)
Si vous voulez retrouver les quatres points composants l'emetteur, fort simple :
// 1er : recup des parametres width & height


float width  ;
float height ;

part->pblock->GetValue(PB_EMITTERWIDTH,0,width,FOREVER);
part->pblock->GetValue(PB_EMITTERHEIGHT,0,height,FOREVER);




//on regenere le rectangle formant l'emetteur
//rectangle centré sur l'origine

Point3 p[4];

width /=2.0f;
height/=2.0f;

p[0].x = -width; p[0].y =  height; p[0].z = 0.0f;
p[0].x =  width; p[0].y =  height; p[0].z = 0.0f;
p[0].x =  width; p[0].y = -height; p[0].z = 0.0f;
p[0].x = -width; p[0].y = -height; p[0].z = 0.0f;


//dernier bout : on transforme ces points
//par la matrice de transformation du node
//pour avoir leur coordonnée réele


Matrix3 mat = node->GetNodeTM(0);

for (int i=0;i<4;i++)
  p[i] = p[i] * mat;






Comment récuperer les Spaces Warp ?



Bon, ben la, maintenant qu'on a vu les particules, ben c'est tout con . Le principe est exactement le même. Preuve par l'exemple, le Space Warp "wind" .

Début de la manip, les ID . La SClassID d'un space warp est "WSM_OBJECT_CLASS_ID", et la classId du wind est "Class_ID(WINDOBJECT_CLASS_ID,0)".
Partant de la, et pour tous les nodes :

ObjectState os = node->EvalWorldState(0);	

if (os.obj && os.obj->SuperClassID()==WSM_OBJECT_CLASS_ID) 
{	
	//Space Warp à l'horizon....
	//est-ce un "wind" ?
	if (os.obj->ClassID() == Class_ID(WINDOBJECT_CLASS_ID,0))
	{
		//Ah ben oui, un WIND
	}
}
Maintenant que nous avons la certitude que c'est bien le space warp que nous cherchons, va s'agir de faire cracher à MAX les infos le concernant . Oublions la méthode dite de "la lampe dans les yeux" et passons par un moyen plus conventionnel, les paramBlocks (hé oui, encore...)
SimpleWSMObject *wind = (SimpleWSMObject *)node->GetObjectRef();
wind->pblock->GetValue(PB_STRENGTH,0,strength,FOREVER);
wind->pblock->GetValue(PB_TYPE,0,type,FOREVER);
...
Etant donné que c'est toujours la même chose, je vais faire court . On recast donc en SimpleWSMObject sans soucis ni problème de conscience grâces aux ID, puis on pioche joyeusement dans le paramBlock .




Liens entre space warp et système de particule



Nous avons dans la main droite les Spaces Warp (wind et compagnie), et dans la main gauche, les objets . Comment faire le lien entre les deux ? Comment savoir quel space warp est appliqué sur quel système de particules ?
Partons du principe que vous énumerez les nodes . Ce qui va suivre s'applique pour chaque node trouvé

Etape 1: Verifier que le noeud en cours est bien affecté par un Space Modifier

On va commencer par regarder si le Node reférence un objet avec une SClass "WSM_DERIVOB_CLASS_ID" (indiquant donc que l'objet référencé est modifié par un Space Warp).
Cela se fait de la façon suivante :

for (int i=0;i < node->NumRefs();i++)
{
  ReferenceTarget * refTarget = node->GetReference(i);
  if ( obj2 != NULL && refTarget->ClassID() == Class_ID(WSM_DERIVOB_CLASS_ID,0)) 
  {
    //noeud réference un objet modifié par les Spaces Warps
  }
}


Etape 2: Retrouver les modifiers appliqués a l'objet

A partir de l'objet réferencé (ici c'est refTarget), nous allons retrouver les modifiers appliqués a cet objet .
IDerivedObject *WSMDerObj = (IDerivedObject *) refTarget;

for (int j=0;j < WSMDerObj->NumModifiers();j++)
{
	Modifier *tModif = WSMDerObj->GetModifier(j);
	//faire mumuse avec le modifier ici
}
Pour recup les différents param d'un modifier, ca se fait via des paramBlocks . (chercher dans les samples du SDK pour retrouver ce qui vous interesse ..)

Etape 3: retrouver le node du modifier

Bon, on sait que l'objet est modifié, on a les modifiers . Ils peut etre utile maintenant de retrouver le noeud référencant le modifier, pour la bonne raison que les TM sont stockés dans le noeud (ou pour n'importe quelle autre raison, finalement, vous faites ce que vous voulez).

La je m'avance un peu, j'ai pas trouvé de doc ou c'etait écrit noir sur blanc, mais il semble que ca marche : vous énumérez les références contenus dans le modifier, la deuxième est la bonne (enfin, celle d'indice 1).
En code, ca ressemblerait à:
INode *baseNode = (INode *)tModif->GetReference(1);
baseNode etant donc le node ou est stocké le space warp....


Etape 4: résumé de l'affaire

for (int i=0;i < node->NumRefs();i++)
{
  ReferenceTarget * refTarget = node->GetReference(i);

  if ( obj2 != NULL && refTarget->ClassID() == Class_ID(WSM_DERIVOB_CLASS_ID,0)) 
  {
    IDerivedObject *WSMDerObj = (IDerivedObject *) refTarget;
    for (int j=0;j < WSMDerObj->NumModifiers();j++)
    {
	Modifier *tModif    = WSMDerObj->GetModifier(j);
	INode *nodeModifier = (INode *)tModif->GetReference(1);
    }
  }
}
Ouf, on à nos liens....