De MD2-file met animatie laden

 

In deze tutorial ga ik jullie vertellen hoe je een ge-animeerde MD2 file moet loaden.

LET OP: De niveau van deze tutorial is voor de programmeurs die al basic 3D kunnen.

De MD2-file structures

De MD2-formaat is, vind ik, simpeler dan de X-file om te loaden. Het enige wat je nodig hebt is een paar structures en de databuffers.

Hier de structs, LET OP: de grootte van de variabelen moeten overeenkomen:

 

//------------------------------------------------

// struct SHeader: De structure voor de file-info

typedef struct

{

      int magic;              // integer(4byte): filetype ID          

      int version;            // integer(4byte): filetype versie      

      int skinWidth;          // integer(4byte): de breedte van de textures

      int skinHeight;         // integer(4byte): de hoogte van de textures

      int frameSize;          // integer(4byte): de grootte van één KeyFrame

      int numSkins;           // integer(4byte): de hoeveelheid textures

      int numVertices;        // integer(4byte): de hoeveelheid vertex in één KeyFrame

      int numTexCoords;       // integer(4byte): de hoeveelheid texturecoordinaten

      int numTriangles;       // integer(4byte): de hoeveelheid polygonfaces(driehoeken)

      int numGlCommands;      // integer(4byte): OpenGL rendering commands, gebruiken we niet 

      int numFrames;          // integer(4byte): de hoeveelheid KeyFrames in totaal     

      int offsetSkins;        // integer(4byte): het addres in de file voor de texture/skin-data

      int offsetTexCoords;    // integer(4byte): het addres in de file voor de texture coordinaten

      int offsetTriangles;    // integer(4byte): het addres in de file voor de polygon data

      int offsetFrames;       // integer(4byte): het addres in de file voor de frame data

      int offsetGlCommands;   // integer(4byte): het addres in de file voor de OpenGL rendering commands

      int offsetEnd;          // integer(4byte): het addres voor het einde (van de wereld)

}SHeader;

//-----------------------------------------------------

// struct SSkin: De structure voor texturebestand

typedef struct

{

      char skinName[64];

}SSkin;

//-----------------------------------------------------

// struct STexCoords: De structure texture coordinaten

typedef struct

{

      short u, v;

}STexCoords;

//----------------------------------------------------

// struct STriangle: De structure voor polygons

typedef struct

{

      short vertexIndex[3];

      short textureIndex[3];

}STriangle;

//-----------------------------------------------

// struct SVertex: De structure voor vertices

typedef struct

{

      float vertex[3];

      float normal[3];

}SVertex;

//---------------------------------------------

// struct SFrame: De structure voor KeyFrame

typedef struct

{

      char  frameName[16];

      SVertex*    pVertices;

}SFrame;

//--------------------------------------------------------------------------------

// struct SSequence: De structure voor één animatie sequence(scene, bijv. “run”)

typedef struct

{

      long start, end;

      char name[16];

}SSequence;

//-----------------------------------------------------------------

// struct SAnimation: De structure om alle sequences op te slaan

typedef struct

{

      long numSequences;

      SSequence* pSequences;

}SAnimation;

//-----------------------------------------------!!!!!!!!!!!!!!!!!!!!!

// struct SAliasVert: De structure om de MD2-vertices op te slaan

typedef struct

{

      byte vertex[3];

      byte lightNormalIndex;

}SAliasVert;

//-----------------------------------------------!!!!!!!!!!!!!!!!!!!!!

// struct SAliasFrame: De structure om de MD2-KeyFrames op te slaan

typedef struct

{

      float scale[3];

      float translate[3];

      char name[16];

      SAliasVert aliasVerts[1];

}SAliasFrame;

 

Met deze structs kun je de alleen MD2-data laden. Want je hebt nog de standaard dingen zoals de texturebuffer, vertexbuffer en de CustomVertex(de ingame vertexstruct) nodig.

Als je goed hebt gekeken, zie je dat er 2 soorten Vertex & Frame structures zijn, nl. de Normale Structures en de Alias Structures.

Deze Alias Structures gebruik je om de MD2-file data DIREKT in te lezen. Om bij de animatie-calculatie minder CPU te gebruiken, laden we de data in deze structures, en doen we die gecalculeerd in de Normale Structures. Dan gebruiken we bij de animatie-calculatie de Normale Structures voor de vertices, die al ge-scaled en ge-translate zijn.

Wat ook erg belangrijk is, zijn de variabeltypen, deze MOETEN gelijk zijn met de bovenstaande. Anders kan het zo zijn dat je te veel of te weinig data leest. Dus kijk goed naar de structures als je ze overtypt.

 

Als laatst voor dat je gaat loaden, als je een loop in een loop hebt vergeet dan niet de binnenste loop te resetten naar 0,

dit zorgde bij mij voor rare vertices. Ik zal het nog maals bij de code in de comments zetten.

 

 

De MD2-file lezen

Je hebt 4 functies nodig om de data voor te bereiden, nog één als je ook de DX/OGL-data klaar wil. De eerste functie is LoadDataFromFile( char* ), deze functie leest de MD2-file. De tweede functie is SetupAnimation(), deze functie maakt de animatie structs klaar. De driede (=P) functie is Animate(). De vierde functie is Unload(). We beginnen met de eerste functie.

 

Globaal

Deze variabelen moet je globaal, of in een class stoppen. Ze worden namelijk door meerdere functies gebruikt.

 

            SHeader           Header;           // File info, GEEN POINTER

            SSkin*            pSkins;           // Textures

            STexCoords*       pTexcoords;       // Texture Coordinaten

            STriangle*        pTriangles;       // Polygon data

            SFrame*           pFrames;          // Animatie frames

            SAnimation        Animations;       // Animatie sequences(scenes)

 

LoadDataFromFile( char* FileName )

Deze functie leest data van de file naar de structures. Nogmaals: LET OP DE LOOP RESETS. De stappen zijn:

 

1)                 De MD2-file openen.

2)                 De SHeader eruit lezen.

3)                 Geheugen vrijmaken volgens de header.

4)                 Naar het addres van de Skins gaan, en lezen.

5)                 Naar het addres van de Texture Coordinaten gaan, en lezen.

6)                 Naar het addres van de Triangles gaan, en lezen.

7)                 Naar het addres van de KeyFrames gaan, en lezen (LET OP: LEES IN DE ALIAS STRUCTURES).

8)                 De data uit de Alias Structures gecalculeerd kopiëren naar de Normale Frame/Vertex Structures.

Hierbij moet je wel de Z en de Y as omdraaien, want MD2 is anders dan normaal.

9)                 De MD2-file sluiten.

 

Zie hier de gecommente code:

 

HRESULT LoadDataFromFile( char* FileName )     // filenaam

{

      FILE* File;

 

      // Open de file in binary mode

      File = fopen( _Name, "rb" );

 

      // Check of file klaar is, anders breken we de operatie af

      if( !File )

      {

MsgBox( "Can't read file." );

return E_FAIL;

            }

     

      // Lees de header

      fread( &Header, 1, sizeof( SHeader ), File );

     

      // Check of versie goed is

      if(Header.version != 8)

      {

            // Als de versie niet 8 is, breken we de operatie af

            MsgBox("Invalid file format (Version not 8)");

            return E_FAIL;

      }

     

      // Maak memory voor de data

      pSkins      = new SSkin       [ Header.numSkins       ];

      pTexcoords  = new STexCoords  [ Header.numTexCoords   ];

      pTriangles  = new STriangle   [ Header.numTriangles   ];

      pFrames     = new SFrame      [ Header.numFrames      ];

 

      // Tijdelijke opslagbuffer, deze is nodig om de ALIAS FRAMES in op te slaan

      // zonder memory te halen uit de RAM (new & delete)

      unsigned char largebuffer    [ 9999 ];  

 

      // Spring naar offset van skins

      fseek( File, Header.offsetSkins, SEEK_SET );

      // Laad de skins

      fread( pSkins, sizeof(SSkin), Header.numSkins, File );

 

      // Spring naar offset van texturecoords

      fseek( File, Header.offsetTexCoords, SEEK_SET );

      // Laad de texturecoords

      fread( pTexCoords, sizeof( STexCoords ), Header.numTexCoords, File );

     

 

      // Spring naar de offset van de triangles

      fseek( File, Header.offsetTriangles, SEEK_SET );

      // Laad de triangles

      fread( pTriangles, sizeof( STriangle ), Header.numTriangles, File );

 

      long i = 0, j = 0;

 

      // SAliasFrame is de frame BINNEN in de MD2, point naar de opslagbuffer

      SAliasFrame* TmpFrame = (SAliasFrame*)largebuffer;

      // Deze Vertexpointer is om minder de-referenties te maken, is sneller

      SVertex* TmpVertex = NULL;

 

      // Spring naar de offset van de frames

      fseek( File, Header.offsetFrames, SEEK_SET );

      // Loop door de KeyFrames om vertex data in te lezen

      for( i=0; i < Header.numFrames; i++ )

      {

            // Maak ruimte voor de data in de frames

            pFrames[i].pVertices = new SVertex[ Header.numVertices ];// SFrame is de frame ingame

 

            // Lees de SAliasFrame data

            fread( TmpFrame, 1, Header.frameSize, File );

 

            // Kopieer de namen

            strcpy( pFrames[i].frameName, TmpFrame->name );

 

            // Point, nu hebben we minder de-referenties in de calculatie, is sneller

            TmpVertex = pFrames[i].pVertices;

           

            // LET OP: reset de j variabel

            for( j=0; j < Header.numVertices; j++ )

            {

                  // LET OP: Draai ff de Z en de Y as om, MD2 heeft een andere assen systeem.

                  // Hier wordt eenmalig de positie gecalculeerd, als dat in de animatie functie.

 

                  // Kopieer de X waarde van de ALIAS FRAME, naar de echte frame

                  pFrames[i].pVertices[j].vertex[0]=       (TmpFrame->aliasVerts[j].vertex[0]

                                                           * TmpFrame->scale[0]

                                                           + TmpFrame->translate[0]);      // X

                  // Kopieer de Z waarde van de ALIAS FRAME, naar de echte frame

                  pFrames[i].pVertices[j].vertex[2]= -1*   (TmpFrame->aliasVerts[j].vertex[1]

                                                           * TmpFrame->scale[1]

                                                           + TmpFrame->translate[1]);       // Z

                  // Kopieer de Y waarde van de ALIAS FRAME, naar de echte frame

                  pFrames[i].pVertices [j].vertex[1]=       (TmpFrame->aliasVerts[j].vertex[2]

                                                           * TmpFrame->scale[2]

                                                           + TmpFrame->translate[2]);        // Y

            }

      }

      // Sluit de file

      fclose( File );

 

      // Succes! =D

      return S_OK;

}

 

Simpel toch? Nogmaals, Z en de Y as omdraaien, en de “j”-loop resetten.

 

SetupAnimation()

Deze functie maakt de animatie structures klaar voor animatie. Deze structures hebben simpelweg gewoon variabelen om de KeyFrame nummers op te slaan en de KeyFrame naam.

Een sequence is een animatie, is een aantal KeyFrames, is een “scene”.

Laten we de structures beter bekijken:

 

//--------------------------------------------------------------------------------

// struct SSequence: De structure voor één animatie sequence(scene, bijv. “run”)

typedef struct

{

      long start, end;

      char name[16];

}SSequence;

//-----------------------------------------------------------------

// struct SAnimation: De structure om alle sequences op te slaan

typedef struct

{

      long numSequences;

      SSequence* pSequences;

}SAnimation;

           

Zoals je ziet, bezit SAnimation een pointer van SSequence. Deze heeft 3 variabelen.

De start variabel slaat de nummer van de KeyFrame op, waar de sequence begint.

De end variabel, waar het eindigt.

De naam variabel voor de naam, logisch?

 

In de KeyFrames (SFrame), zijn de namen van de frames de naam van de sequence + de nummer.

Als we dus ff nadenken, kunnen we de nummers eraf halen en de resultaat opslaan als sequencenaam.

Dit zijn de stappen om de animaties klaar te maken:

 

1)     Tel de aantal verschillende sequences.

2)     Geheugen vrijmaken voor de sequences.

3)     Ga door de sequences, sla de namen en de start/end KeyFrame-nummers op.

 

Het lijkt simpel maar in code is het nogal ingewikkeld:

 

HRESULT SetupAnimation()

{

      char Name[16]={0};           // Naam buffer voor de sequence naam

      char LastName[16]={0};       // Vorige KeyFrame naam, om te check of het tot dezelfde sequence behoort

      char FirstName[16]={0};       // De allereerste sequence naam

      long i = 0, j = 0;          

      long length = 0;

 

      // Zet op 0

      Animations.numSequences = 0;

 

      // Om te weten hoeveel sequences er zijn, loopen we door de frames

      // en checken we de namen.

      // LOOP: door de KeyFrames

      for( i=0; i < Header.numFrames; i++ )

      {

            // Kopieer de framenamen, als we dit niet doen, kunnen we de Framenamen wijzigen.

            // Dit willen we niet want we willen de Frames niet meer wijzigen.

            strcpy( Name, pFrames[i].frameName );

 

            // Het aantal letters

            length = strlen(Name);

 

            // Zoek de nummers uit de naam (bijv. “frame01” -> “frame” )

            // LET OP: reset de loop variabel 

            for( j=0; j < length; j++ )

            {

                  // Kijk of het een nummer is

                  if( (Name[j] < 'A') || (Name[j] > 'z' ) )

                  {

                       // Verwijder de nummers

                       Name[j] = '\0';  

                      

                       // Als deze NIET bij de sequence hoort van de vorige KeyFrame

                       if( strcmp( LastName, Name ) )

                        {

                             // Dan is er een nieuwe vorige sequence

                             // Kopieer de nieuwe sequence naam (deze heeft nu GEEN nummers)

                             strcpy( LastName, Name );

                             // Vergroot de aantal sequences

                             Animations.numSequences++;

                       }

                        // Als FirstName WEL leeg is(dus is dit de eerste keer)

                       // dan is dit de eerste frame

                        if( !strcmp( FirstName, "" )

                             strcpy( FirstName, Name );

                      

                       // Klaar met deze naam(en frame)

                       break;     

                  }

            }

      }

      // We hebben het aantal sequences van deze MD2 gevonden.

      // LET OP: we hebben nog geen info opgeslagen, alleen sequences geteld!!!!

      // OOK HEBBEN WE GEEN NAMEN OPGESLAGEN, ALLEEN SEQUENCES GETELD!!!!

      // Nu moeten we data vrij maken om alle sequence-info op te slaan.

      Animations.pSequences = new SSequence[ Animations.numSequences ];

 

      // Wis namen, hergebruik straks

      strcpy( Name, "" );                     

      strcpy( LastName, FirstName );

     

      // Sequence counter, deze wordt vergroot als er info

      // van een nieuwe sequence wordt opgeslagen

      long CurSequence=0;

 

      // Maak de eerste sequence alvast hà-kûh-laár(oftewel “klaar”)

      Animations.pSequences[0].start = 0;

      strcpy ( Animations.pSequences[0].name, FirstName );

 

      // Reset counters

      i = j = length = 0;

 

      // LOOP: door de KeyFrames

      for( i=0; i < Header.numFrames; i++ )

      {

            // Kopieer namen, weer.

            strcpy( Name, pFrames[i].frameName );

            length = strlen( Name );

 

            // Loop door de naam

            // LET OP: RESET LOOP VARIABEL

            for( j=0; j < length; j++ )

            {

                  // Kijk of het een nummer is

                  if( (Name[j] < 'A') || (Name[j] > 'z' ) )

                  {

                        // Verwijder de nummers

                        Name[j] = '\0';

                      

// Als deze NIET bij de sequence hoort van de vorige KeyFrame

                        if( strcmp( LastName, Name ) )

                       {

                             // HIER IS EEN NIEUWE SEQUENCE!!

 

                             // Beëindig de sequence, min één want

                              // DEZE FRAME HOORT AL BIJ DE VOLGENDE SEQUENCE

                             Animations.pSequences[CurSequence].end = i-1;

                             CurSequence++;

                              // Ga verder bij de volgende sequence

                              Animations.pSequences[CurSequence].start = i;

                              // Kopieer nieuwe sequence naam

                              strcpy( LastName, Name);

                             // Kopieer de naam ook in de data

                             strcpy( Animations.pSequences[ CurSequence ].name, Name);

                       }

                        // Als het dezelfde sequence(set van animatieframes)

                        else

                       {

                              // De end variabel is één frame langer geworden

                             Animations.pSequences[CurSequence].end = i;

                       }

                        // klaar met de naam

                       break;     

                  }

                 

            } // ENDLOOP: door de naam

      } // ENDLOOP: door de KeyFrames

      // Succes! =D

      return S_OK;

}

 

Nu hebben we voor elke sequence(scene) de beginKeyFrame-nummer en de eindKeyFrame-nummer, genoteerd.

Ook de naam natuurlijk. Nu moeten we deze nummers gebruiken om een sequence te renderen, loopend.

 

Nu even snel de Unload() functie. Altijd handig als je niks wilt crashen =D

 

VOID CMD2::UnloadModel()

{

#define DeleteBuffer( x ) if( x ){ delete[] x; x = NULL; }

long i=0;

      // We gaan door de frames heen en verwijderen de vertexdata

      for( i=0; i<Header.numFrames; i++ )

      {

            DeleteBuffer( pFrames[i].pVertices );

      }

      // We verijderen alle andere data

      DeleteBuffer( pFrames );

      DeleteBuffer( pTriangles ); 

      DeleteBuffer( pTexcoords ); 

      DeleteBuffer( pSkins );

      DeleteBuffer( Animations.pSequences );

 

      return;

}

 

Elke frame(in de gameloop, niet als animatie=D) moet je nu met een functie, de Animatie roepen. Je switcht gewoonweg de KeyFrame. Maar om de animatie smooth te laten verlopen, heb je een calculatie nodig. Die zie je in de volgende sectie. De Graphics code( vertexbuffer ) is in DirectX, maar de calculaties en de functie-verloop is hetzelfde als bij OpenGL. LET OP: Bij OpenGL zijn de TextureCoordinaten anders. Zie de calculatie in de DirectX sectie, hier staat het gecomment.

 

Smooth Animation calculatie (gedeeltelijk DirectX)

In deze sectie gaan we 2 dingen doen. Eerst gaan we DirectX9 klaar maken, de textures en de vertexbuffer.

Daarna gaan we de KeyFrame calculatie maken, waar we smooth animating toepassen.

Om te renderen hebben we een ingame Vertex Structure nodig, wordt gebruikt om te renderen. De MD2-data Vertex Structure is niet geschikt voor DirectX om te renderen. Hier is de CustomVertex Structure:

 

//------------------------------------------

// struct CustomVertex: onze ingame vertices

typedef struct

{

      float x,y,z;

      float u,v;

}CustomVertex;

//------------------------------------------

// define CustomDesc: fixed vertexs function

#define CustomFVF (D3DFVF_XYZ | D3DFVF_TEX1)

 

Ik heb ook gelijk de CustomFVF erbij geschreven die aangeeft dat we 3D-posities en een texture gebruiken.

Omdat DirectX niks met de MD2 Vertex Structure kan, moeten we de data kopiëren in de Ingame Vertex Structure.

 

SetupDirectX()

Één van de problemen met MD2, is dat je de texturefilename in de SSkin kan vinden, maar dat veel Modelprogramma’s het niet gebruiken!

In dat geval moet je de texturefilename zelg doorgeven en dan zelf loaden. Veel te veel moeite als je het mij vraagt.

Dus pak MilkShape3D, en kijk ff naar mijn MD2 Export Tutorial, waar je leert om ge-animeerde MD2’s te exporteren.

MilkShape3D export dan ook gelijk de texturefilename.

We hebben alle MD2-data klaar. Nu moeten we nog DirectX klaar maken om goed te kunnen renderen.

We hebben onder andere ruimte nodig voor een paar textures(een character kan meerdere skins hebben), en ruimte nog voor de vertices.

Deze functie is tot nu toe de allersimpelste, maar je hebt wel basic Direct3D kennis nodig:

 

HRESULT CMD2::SetupDX()

{

      HRESULT r;

 

      // Als er skins zijn

      if( Header.numSkins > 0 )

      {

      // Maak ruimte voor de textures(skins)

            pTexture = new LPDIRECT3DTEXTURE9[ Header.numSkins ];

            // Laad de textures(skins)

            for( short i=0; i < Header.numSkins; i++ )

            {

                  r = D3DXCreateTextureFromFile( pDevice, pSkins[i].skinName, &pTexture[i] );

                  if( r != D3D_OK )

                        pTexture[i] = NULL;

            }

      }

else

      {

            MsgBox( "Geen textureskins gevonden.");

      }

 

      // Maak een Vertex buffer

      r = pDevice->CreateVertexBuffer( Header.numTriangles * 3 * sizeof( CustomVertex ), 0,

                                                CustomFVF, D3DPOOL_MANAGED, &pVertexBuffer, NULL );

      if( r != D3D_OK )

            MessageBox( hWnd, "VB", "CMD2 Error", MB_OK|MB_ICONERROR );

 

      // Succes! =D

      return S_OK;

}

 

 

Animate()

Fijn, we zijn al klaar met alles. MD2 data geload, DirectX data klaar gemaakt, nu nog de animatie!

Nu denk je natuurlijk dat we elke gameframe(in de gameloop) gewoon een KeyFrame erbij doen, dan krijg je automatisch een filmpje.

Dat zou fijn zijn! Maar het gaat een tikkie anders! Wat dacht je nou als je op de snelste computer ter wereld de model rendert?

Dan zou je de animatie niet eens kunnen volgen!

De oplossing is om tijd in de Animate() functie te proppen. Het verschil van de KeyFrames wordt vermenigvuldigd met de tijd.

Dan krijg je dus animatie op tijd gebaseerd.

 

HRESULT Animate( long _Sequence )  // parameter: sequence(scene) nummer

{

      // Zorg dat _AnimKey correct is, als het teveel is,

      // dan halen we de totaal ervan af

      _Sequence %= Animations.numSequences;

 

      // Tijd variabelen

      float Speed = 10.0f;         // Dit is de standaard snelheid

      static float Last = 0;       // Dit is de laatste keer dat we updateten(=P)

      float Current = (float)GetTickCount();   // Dit is de tijd

      float Elapsed = Current-Last;            // Dit is het verschil in de 2 tijden

      Elapsed = Elapsed / (1000.0f / Speed );  // Deze calculatie is voor de KeyFrame calculatie verderop    

 

      if( Elapsed > 1.0f )    // Update als Elapsed meer dan 1 is

      {

            Last = Current;                    // Tijd

nCurrentFrame = nNextFrame;        // Update

            nNextFrame += 1;                   // Update

            Elapsed = 0.0f;                    // Dit is voor de volgende KeyFrame, dus er is geen tijd vergaan

 

            if( nCurrentSequence != _Sequence )      // Als we van sequence moeten veranderen

            {

                  nNextFrame = Animations.pSequences[ _Sequence ].start;     // Laten we de volgende frame de

                  nCurrentSequence = _Sequence;                        // nieuwe sequence zien, en is de huidige

            }                                                          // sequence veranderd naar de volgende

      }    

 

      // Als de huidige frame de volgende keyframe is

      if( nCurrentFrame == nNextFrame )

            nNextFrame++;                            // is de volgende eentje erna

 

      // Als de huidige keyframe de laatste keyframe van de sequence is,

// en dus is de volgende voorbij de laatste, maken we de volgende de eerst keyframe

// van de sequence

      if( nNextFrame > Animations.pSequences[ _Sequence ].end )

            nNextFrame = Animations.pSequences[ _Sequence ].start;           // is de laatste de eerste

 

      // Loop tellers

      long        tris=0, verts=0;

 

      // Vertexbuffer datapointer, hier wordt de VB ingelocked

      void*       memory = NULL;

 

      // Deze pointer, wijst naar memory, en kan de vertexbuffer veranderen

      CustomVertex* VertexPtr;

 

      // Weer een pointer, maakt de loop sneller, maar is niet nodig

      SVertex*    CurrentVertices = NULL;

      // Weer een pointer, maakt de loop sneller, maar is niet nodig

      SVertex*    NextVertices = NULL;

     

      // Lock Vertexbuffer

      pVertexBuffer->Lock(0,0,&memory,0);

     

      // “memory” is nu een void pointer, we typecasten het om

      // variabelen die in CustomVertex zitten, te accessen.

      // Zonder typecast zou de compiler vervelend zijn.

      // Maak pointer naar Vertexbuffer memory

      VertexPtr = (CustomVertex*)memory;

 

      // Maak pointer naar de frames, waar alle vertex data is

      CurrentVertices = pFrames[nCurrentFrame].pVertices;

      NextVertices = pFrames[nNextFrame].pVertices;

 

      // Kopieer data!!!!! Loop door de polygonen!

      // LET OP: Niet door de KeyFrames loopen, we hebben vertexIndexen nodig

      // Dit kan alleen worden uitgerekend door door de polygonen en de vertices

      // te loopen!!!!!!

      for( tris=0; tris < Header.numTriangles; tris++ )

      {

            // LET OP: verts=0 (reset)

            for( verts=0; verts < 3; verts++ )

            {

                  // Dit is de index in de polygonendata van de huidige vertex,

// voor de indexbuffer in de MD2-KeyFrames

                  long vertexIndex = pTriangles[tris].vertexIndex[verts];

 

                  // Hier kopiëren we de vertex data van de huidige KeyFrame

                  float x1 = CurrentVertices[vertexIndex].vertex[0];

                  float y1 = CurrentVertices[vertexIndex].vertex[1];

                  float z1 = CurrentVertices[vertexIndex].vertex[2];

                 

                  // Hier kopiërem we de vertex data van de volgende KeyFrame

                  float x2 = NextVertices[vertexIndex].vertex[0];

                  float y2 = NextVertices[vertexIndex].vertex[1];

                  float z2 = NextVertices[vertexIndex].vertex[2];

 

                  // Hier worden de kopiën gebruikt om de huidige stand te calculeren

                  // met de tijd. Als we dit niet deden, zou je houterige animaties hebben.

                  VertexPtr[tris*3+verts].x = x1 + Elapsed * ( x2 - x1 );

                  VertexPtr[tris*3+verts].y = y1 + Elapsed * ( y2 - y1 );

                  VertexPtr[tris*3+verts].z = z1 + Elapsed * ( z2 - z1 );

                  LowestY = min( VertexPtr[tris*3+verts].y, LowestY );

 

                  // Dit is de index van de texture van deze vertex,

                  // voor de texturecoordinates in de MD2-KeyFrames

                  long textureIndex = pTriangles[tris].textureIndex[verts];

 

                  // Kopiëren we de UV coordinaten gedeeld door de breedte

                  // en de hoogte van de texture zelf. Hierdoor krijgen we

                  // UV coordinaten tussen de 0 en de 1.

 

                  // Kopiëer de U coordinaat

                  VertexPtr[tris*3+verts].u = pTexcoords[textureIndex].u / (float)Header.skinWidth ;

                 

                  // Kopiëer de V coordinaat, LET OP: OpenGL gebruikers plaatsten hier:

                  // VertexPtr[tris*3+verts].v = 1 - pTexcoords [textureIndex].v / (float)Header.skinHeight;

                  VertexPtr[tris*3+verts].v = pTexcoords[textureIndex].v / (float)Header.skinHeight;

            }

      }

      // Unlock de VertexBuffer

      pVertexBuffer->Unlock();

     

      // Succes! =D

      return S_OK;

}

 

Okee, nu hebben we een functie die we elke frame (inde gameloop) roepen. Deze maakt de vertexbuffer volgens de tijd + KeyFrame.

Wat we nu nodig hebben is de Render() functie. Dan hebben we in de gameloop dus 2 functies die we moeten roepen:

Animate() en Render(). Deze functie laat ik maar aan jou over want het verschilt nogal per project.