La Tri Api

Écrit le 03/07/2003 par Crono
Dernière mise à jour : 30/01/2006

La Tri Api

Voilà, comme promis, un tutorial sur la Tri Api... Je n'avais pas compris son principe jusqu'au moment ou j'ai plongé mon nez dans de la programmation OpenGL... Je pense maintenant être apte à vous en parler.
Tout d'abord qu'est ce donc que cette chose ?!? Eh bien c'est une api client pour générer des triangles... Mais pourquoi des triangles me direz vous ? Car toutes les formes peuvent être générées par des triangles... Géosphère, sphère, cube... bref tout :) mais rassurez-vous, la Tri Api permet aussi de faire des polygones, des quadrilatères et des lignes.
Maintenant, voyons le fonctionnement de cette api. Pour cela, je vais vous expliquer le fonctionnement du fichier tri.cpp disponible dans le sdk 2.0 :

// Triangle rendering, if any
#include "hud.h"
#include "cl_util.h"
// Triangle rendering apis are in gEngfuncs.pTriAPI
#include "const.h"
#include "entity_state.h"
#include "cl_entity.h"
#include "triangleapi.h"
#define dllexport __declspec( dllexport )

extern "C"
{
void dllexport HUD_DrawNormalTriangles( void );
void dllexport HUD_DrawTransparentTriangles( void );
};


//#define TEST_IT
#if defined( TEST_IT )

Ici, on teste si TEST_IT est défini... Et comme vous pouvez le voir, la ligne #define TEST_IT est commentée. Vous devrez donc enlever les // afin de déclarer la fonction Draw_Triangles().

/* =================
Draw_Triangles Example routine.
Draws a sprite offset from the player origin.
================= */

void Draw_Triangles( void )
{
    cl_entity_t *player;
    vec3_t org;
    // Load it up with some bogus data
    player = gEngfuncs.GetLocalPlayer();

    if ( !player ) return;

Jusque là, rien de très dur : On déclare le joueur, on déclare un nouveau vecteur org, a trois composantes (x, y, z), on sélectionne le joueur et on s'assure que c'est bien un joueur.

org = player->origin;
    org.x += 50;
    org.y += 50;

Ici, on recopie la position du joueur dans le vecteur org, et on incrémente les composantes x et y de 50.

    if (gHUD.m_hsprCursor == 0)
    {
        char sz[256]; sprintf( sz, "sprites/cursor.spr" );
        gHUD.m_hsprCursor = SPR_Load( sz );
    }
    if( !gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *)gEngfuncs.GetSpritePointer( gHUD.m_hsprCursor ), 0 ))
    {
        return;
    }

Ici, on vérifie que le sprite qui va texturer notre polygone existe bien. Si ce n'est pas le cas, la fonction ne s'exécutera pas et le jeu ne se lancera pas. Avec la fonction SpriteTexture(), on sélectionne le sprite qui texturera notre polygone.

    // Create a triangle, sigh
    gEngfuncs.pTriAPI->RenderMode( kRenderNormal );

Ici, on définit le type de rendu du polygone. Plusieurs choix sont possibles :

kRenderNormal : Rendu sans transparence.

kRenderTransAdd : Rendu transparent de type Additif.

kRenderTransAlpha : Rendu transparent de type Alpha.

    gEngfuncs.pTriAPI->CullFace( TRI_NONE );

Ici, on choisit la direction des normales... Késako ça les normales ? Et bien c'est un vecteur imaginaire perpendiculaire a une face et qui défini sa visibilité. Bref c'est un sujet assez complexe... Les formules mathématiques pour calculer leur direction le sont encore plus... Heureusement, Valve nous a simplifié la vie et nous avons 2 choix pour leurs directions :

TRI_NONE : Les normales sont projetées des 2 cotés de la face... La face sera donc visible des 2 cotés. Et de ce fait, au lieu d'y avoir 1 face, il y en aura 2 car il est impossible de projeter 2 normales à partir d'une seule face. Sachez donc qu'en utilisant TRI_NONE vous doublez le nombre de faces :

http://www.game-lab.com/images/tuts/hl1_triapi/6-2.jpg

TRI_FRONT : Son nom en dit pas mal... La face ne sera visible que de devant. Si vous vous placez derrière, vous verrez à travers. Etant donné qu'un seule normale est projetée, une seule face est calculée :

http://www.game-lab.com/images/tuts/hl1_triapi/6-3.jpg
    gEngfuncs.pTriAPI->Begin( TRI_QUADS );

Nous attaquons maintenant la partie création de face. La fonction Begin() indique que nous allons envoyer un certain nombre d'informations au moteur 3D. L'argument de cette fonction va définir le nombre de Vertex par face et leur type :

TRI_TRIANGLES : ou TRIANGLES INDEPENDANTS : Le moteur va attendre des packet de trois vertex, et va crée un triangle par packet :

http://www.game-lab.com/images/tuts/hl1_triapi/6-6.jpg

TRI_TRIANGLE_STRIP : Les trois premiers vertex qui seront créés formeront le triangle de base. Ensuite pour chaque nouveau vertex, le moteur formera un triangle avec les 2 deux vertex du triangle précédent et avec le nouveau vertex :

http://www.game-lab.com/images/tuts/hl1_triapi/6-4.jpg

Sur cette image, les vertex 1,2 et 3 forment le triangle de base. Ensuite lorsqu'on crée le vertex 4, un triangle entre les vertex 2,3 et 4 se forme. Enfin lorsqu'on crée le vertex 5, un triangle est formé avec les vertex 3,4 et 5.

TRI_TRIANGLE_FAN : Le principe est le même que celui des TRI_TRIANGLE_STRIP sauf que le nouveau vertex créé après le triangle de base créera un triangle entre le premier vertex et le dernier vertex du triangle précédent et ce nouveau vertex :

http://www.game-lab.com/images/tuts/hl1_triapi/6-5.jpg

Sur cette image, les vertex 1,2 et 3 forment le triangle de base. Lorsqu'on crée le vertex 4, un triangle est formé non pas entre 2,3 et 4 comme avec les TRI_TRIANGLE_STRIP, mais entre 1,3 et 4. Et donc lorsqu'on crée le vertex 5, un triangle se forme entre 1, 4 et 5.

Dans le cas des TRI_TRIANGLE_STRIP et TRI_TRIANGLE_FAN, le moteur 3D va attendre un packet de 3 vertex pour former le premier triangle et va ensuite attendre les vertex uns par uns. Les triangles formés avec TRI_TRIANGLES sont indépendants les uns des autres contrairement a ceux formés avec TRI_TRIANGLE_STRIP et TRI_TRIANGLE_FAN.

TRI_QUADS : ou QUADRILATERES INDEPENDANTS : Le moteur va attendre des paquets de 4 vertex, et va crée des quadrilatères avec ces 4 vertex :

http://www.game-lab.com/images/tuts/hl1_triapi/6-7.jpg

TRI_QUAD_STRIP : Les quatre premiers vertex vont former le quadrilatère de base. Ensuite les 2 prochains vertex formeront un quadrilatère avec les deux derniers vertex du quadrilatère précédent :

http://www.game-lab.com/images/tuts/hl1_triapi/6-8.jpg

Sur cette image, les vertex 1,2,3 et 4 forment le quadrilatère de base. Lorsqu'on crée les vertex 5 et 6, un quadrilatère sera crée entre les vertex 3,4,5 et 6.

Dans le cas du TRI_QUAD_STRIP, le moteur va attendre un paquet de 4 vertex suivi de paquet de 2 vertex. Comme pour les TRI_TRIANGLES, les quadrilatères formés avec TRI_QUAD sont indépendants les uns des autres.

TRI_LINES : Créer un série de lignes indépendantes les unes des autres. Le premier couple de vertex va créer une ligne, le second, une autre ligne, etc. Le moteur attendra donc des paires de vertex. Sachez qu'aucun polygone n'est crée avec cette option... :

http://www.game-lab.com/images/tuts/hl1_triapi/6-9.jpg

Sur cette image, les vertex 1 et 2 forment la première ligne et les vertex 3 et 4 forment la seconde.

TRI_POLYGON : Cette option est la plus complexe : elle sert à créer un polygone en spécifiant ses arrêtes : Le principe est presque le même que celui des TRI_LINES, sauf que les lignes sont unies : Le premier vertex défini l'origine de la première arrête, le second défini la fin de la première arrête et l'origine de la seconde, etc.

Remarque : La fin de la dernière arrête et le début de la première se rejoignent automatiquement :

http://www.game-lab.com/images/tuts/hl1_triapi/6-10.jpg

Dans le cas de TRI_POLYGON, le moteur attend au minimum un packet de 2 vertex pour créer l'arrête de base. Ensuite il attend des vertex seuls.
Remarque : Si vous ne créez que 2 vertex, aucun polygone ne sera crée.

Voilà, c'est tout pour les options de la fonction Begin()... Continuons donc dans le fichier tri.cpp

    // Overload p->color with index into tracer palette, p->packedColor with brightness
    gEngfuncs.pTriAPI->Color4f( 1.0, 1.0, 1.0, 1.0 );

La fonction Color4f() (à 4 arguments de type float) permet de donner une couleur a notre polygone.

Son format est : Color4f(Rouge, Vert, Bleu, Alpha). Les valeurs des arguments vont de 0.0 a 1.0.

    // UNDONE: this gouraud shading causes tracers to disappear on some cards (permedia2)
    gEngfuncs.pTriAPI->Brightness( 1 );

La fonction Brightness() règle la valeur de l'éclairage du polygone.

    gEngfuncs.pTriAPI->TexCoord2f( 0, 0 );

La fonction TexCoord2f() (à 2 arguments de type float) permet de texturer notre polygone. Les valeurs des deux arguments (x et y) vont de 0 à 1. Je vous parlerai de cette fonction plus bas.

    gEngfuncs.pTriAPI->Vertex3f( org.x, org.y, org.z );

La fonction Vertex3f() (à 3 arguments de type float) permet de créer un vertex dans l'espace tridimensionnel. Ses arguments sont x, y et z :

http://www.game-lab.com/images/tuts/hl1_triapi/6-12.jpg

Dans notre exemple, les valeurs sont org.x, org.y et org.z qui correspondent à la position x+50, y+50 et z du joueur dans l'espace.

Voilà, notre premier vertex est créé. La suite d'opération Brightness(), TexCoord2f et Vertex3f est répétée 4 fois. (La fonction BEGIN était argumentée avec TRI_QUAD).

    gEngfuncs.pTriAPI->Brightness( 1 );
    gEngfuncs.pTriAPI->TexCoord2f( 0, 1 );
    gEngfuncs.pTriAPI->Vertex3f( org.x, org.y + 50, org.z );
    gEngfuncs.pTriAPI->Brightness( 1 );
    gEngfuncs.pTriAPI->TexCoord2f( 1, 1 );
    gEngfuncs.pTriAPI->Vertex3f( org.x + 50, org.y + 50, org.z );
    gEngfuncs.pTriAPI->Brightness( 1 );
    gEngfuncs.pTriAPI->TexCoord2f( 1, 0 );
    gEngfuncs.pTriAPI->Vertex3f( org.x + 50, org.y, org.z );

Analysons maintenant la position des vertex dans l'espace : Nous allons prendre le vecteur org comme origine des axes :

Le premier vertex se trouve donc à l'origine. Le second se trouve décalé de 50 sur l'axe y. Le troisième décalé de 50 sur l'axe x et de 50 sur l'axe y. Et enfin le dernier décalé de 50 sur l'axe des x. Nous devrions donc avoir quelque chose qui ressemble à ça :

http://www.game-lab.com/images/tuts/hl1_triapi/6-13.jpg

Plus tard lorsque vous ferez la manip, vous verrez que ce n'est pas ça... A croire que les américains (ou Valve :)) n'ont pas les même normes qu'ici :) Leur plan tridimensionnel semble avoir subi une rotation de 90° selon l'axe x car au résultat nous avons quelque chose qui ressemble à ça :

http://www.game-lab.com/images/tuts/hl1_triapi/6-14.jpg

C'est une question a laquelle je n'ai pas de réponse... J'ai peut être fait une erreur dans mon raisonnement... bref... Maintenant, je vais vous parler de la fonction TexCoord2f. Etant donné que nous savons a quoi ressemble notre polygone, nous pouvons étudier ses coordonnées de textures. En considérant le plan de Valve voici à quoi ressemble notre polygone vu de dessus :

http://www.game-lab.com/images/tuts/hl1_triapi/6-14.jpg

Nous allons imaginer une texture superposée a notre polygone. Prenons par exemple une texture de TFC :

http://www.game-lab.com/images/tuts/hl1_triapi/6-11.jpg

La texture est considérée dans un plan orthogonal unitaire, c'est a dire que sa valeur maximale est 1.0 sur les 2 axes. Maintenant imaginez les deux images précédentes superposées. Le point 1 doit avoir comme coordonnées de texture 0,0, le point 2 : 0,1, le point 3 : 1,1 et le point 4 : 1,0. Les valeurs maximales étant de 1, vous ne pouvez pas dupliquer une texture sur un seul polygone. Voilà je pense avoir expliqué la chose le plus justement possible.

Retournons au code du fichier tri.cpp

    gEngfuncs.pTriAPI->End();

La fonction End() signale la fin de l'envoi d'informations au moteur 3D.

    gEngfuncs.pTriAPI->RenderMode( kRenderNormal );

Ici on s'assure de remettre le mode de rendu par défaut.

}

#endif

/* =================
HUD_DrawNormalTriangles
Non-transparent triangles-- add them here
================= */

void dllexport HUD_DrawNormalTriangles( void )
{
#if defined( TEST_IT )
    // Draw_Triangles();
#endif
}

Pour que la fonction Draw_Triangles() soit exécutée, vous devez décommenter son appel dans une des deux fonctions :

- HUD_DrawTransparentTriangles()
- HUD_DrawNormalTriangles()

/*=================
HUD_DrawTransparentTriangles
Render any triangles with transparent rendermode needs here
=================*/

void dllexport HUD_DrawTransparentTriangles( void )
{
#if defined( TEST_IT )
    // Draw_Triangles();
#endif
}

Remarques : Les deux fonctions ci dessus sont des fonctions permanentes, c'est a dire qu'elles sont exécutées a chaque frame. Autre chose assez importante, logique mais importante :) La fonction telle qu'elle est codée ici affiche un carré chez tous les clients. Mais le client ne vois que son carré... Pas celui des autres clients... Voilà c'est tout.

Dernière remarque promis :) : Regardez bien l'ordre pour créer le polygone :

Configuration du polygone (SpriteTexture(), RenderMode(), CullFace()) -> Begin() -> Config du vertex 1 ( Color(), Brightness(), Texcoord()) -> Vertex 1 -> Configuration du vertex 2... -> End()

J'espère que ce tutorial vous aura éclairé !