Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

Anonymous

unregistriert

1

30.11.2008, 00:17

MD2-Modelloader

C++-Grafikprogrammierung mit OpenGL
MD2-Modelloader Tutorial


Nach längerer Überlegung bin ich zu dem Entschluss gekommen, doch noch ein MD2-Loader Tutorial zu schreiben. Und zwar geht es hier um das berühmte „MD2“ Modelformat, bekannt aus „Quake 2“ (ID Software). Aufgebaut ist es im Binären Format. Es ist relativ einfach zu handhaben und die Importierung ist auch nicht die Welt. Das Format selber unterstützt dazu die recht simple Animation, auf die ich hier allerdings nicht eingehen werde.

Die Projektdatei ist OpenGL basiert und kann mit Visual Studio geöffnet werden. Ich habe versucht den Strukturaufbau so zu gestalten, dass er verständlich und gleichzeitig mit der besten performance zu laden ist.

Damit ihr euch eure eigenen Models erstellen und portieren könnt, werde ich am Ende des Tutorials noch erklären, wie ihr das anstellt. So, nun aber zum eigentlichen Tutorial!

Der Aufbau vom MD2-Format
Nun, dass MD2-Modelformat ist so aufgebaut, dass wenn wir die richtigen Strukturdaten bereitstellen, wir direkt von den Modeldaten in den Arbeitsspeicher (RAM) transferieren können. Außerdem sind in der Datei die geometrischen Daten, die Texturkoordinaten und die Frames. Was unter „geometrischen Daten“ zu verstehen ist, sollte klar sein. Die Texturkoordinaten, beinhalten natürlich die Koordinaten der Textur selbst. Und joa, die Frames sind vorerst nicht ganz so wichtig. Um es kurz zu klären, die verschiedenen Frames des Models halten fest, welche Position etc. gerade das Model haben soll. Das heisst, durch den Ablauf verschiedener Frames ist es uns ermöglicht, eine Animation darzustellen. Da wir in diesem Tutorial aber nur das MD2-Model laden wollen, reicht es, wenn wir den ersten Frame laden. Wie schon gesagt, würden wir mehrere Frames laden, hätten wir eine Animation und das wollen wir nicht.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
class CMD2Model
{
public:
   CMD2Model();
   ~CMD2Model();

   bool  LoadModel(const char* szFilename);
   void  DrawMD2Model();


Wir erstellen die Klasse CMD2Model. Dann erstellen wir Konstruktor und Dekonstruktor. Die brauchen wir um die Instanzen zum späteren einlesen zu leeren. Im Dekonstruktor geben wir die Instanzen dann wieder frei, sprich wir “zerstören” sie (-:.
Außerdem deklarieren wir die Methoden “LoadModel”, um das eigentliche Model zu laden und “DrawMD2Model” in der wir das Model hinterher umberechnen und anschliessend an den Renderingcontext übergeben.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
   struct MD2_Header
   {
       int magic;
       int version;
       int skinWidth;
       int skinHeight;
       int framesize;
       int numSkins;
       int numVertices;
       int numTexCoords;
       int numTriangles;
       int numGlCommands;
       int numFrames;
       int offsetSkins;
       int offsetTexCoords;
       int offsetTriangles;
       int offsetFrames;
                 int offsetGlCommands;
       int offsetEnd;
   };

So, da wären wir bei MD2_Header. Dieser Header lädt alle nötigen Instanzen die wir später brauchen. Wir werden später zur gewünschten Stelle springen und ihn einlesen um die Struktur mit den Daten zu füllen.

“magic”
Diese Instanz enthält die “magische” Zahl. Sie ist in jedem MD2-Model enthalten und enthält den dezimalen Wert “844121161”. Dieser wird gebraucht, damit Modellierungsprogramme erkennen das es sich um ein MD2-Format handelt.
Erkannt wird das Format auch durch "IDP2". Interessiert uns daher nicht.

“version”
Versionsnummer der Modeldatei, interessiert uns ebenfalls noch nicht.

“skinWidth”
Die Breite der Textur.

“skinHeight”
Die Höhe der Textur.

“framesize”
Gibt die Größte eines Frames in Bytes an (1 Byte=8 Bit, siehe Tutorial “Dualzahlen....”).

“numSkins”
Anzahl der Texturen die wir für das Model benötigen.

“numVertices”
Anzahl der Vertices (singular: Vertex (Punkt, Knotenpunkt)).

“numTexCoords”
Anzahl der Texturkoordinaten die für das Model gebraucht werden.

“numTriangles”
Anzahl der Dreiecke im Model.

“numGlCommands”
Anzahl der OpenGL Befehle. Interessiert uns hier noch nicht.

“numFrames”
Anzahl der Frames. Wir werden aber nur den ersten Frame einlesen um die Grundstellung zu rendern.

“offsetSkins”
Zu einem Offset werden wir springen, um von da aus den Datenblock einzulesen. Sprich ein Offset gibt die Position an, ab wo gelesen werden soll. Hier wäre es das Offset für die Texturinformationen.

“offsetTexCoords”
Genau das gleiche wie bei den “offsetSkins”, nur das hier anstatt zu den TexturInformationen, zu den Texturkoordinaten “gesprungen” wird.

“offsetTriangles”
Hier wird wieder zu den Dreiecken gesprungen, damit der Block eingelesen werden kann.

“offsetFrames”
Das Offset für den “Frames”-Block.

“offsetGlCommands”
Stelle zu der gesprungen wird, um die OpenGL-Befehle einzulesen.

So, den Header hätten wir damit schonmal. Weiter geht’s!

C-/C++-Quelltext

1
2
3
4
5
struct MD2_Vertex
   {
       BYTE vertex[3];
       BYTE lightNormalIndex;
   };

So, hier hätten wir dann die Struktur für den Vertex. “BYTE” deklariert eine 1 Byte (=8 Bit) Variable. Wir weisen ihr für die x, y, z-Koordinaten jeweils ein Element zu. Sprich:
vertex[0] = X
vertex[1] = Y
vertex[2] = Z

Wahrscheinlich werden sie sich jetzt fragen, warum wir die Instanzen als Byte deklarieren. Nun, dass liegt daran das die Koordinaten aus Platzgründen skaliert, komprimiert und transformiert werden, doch darauf komme ich später.

Die zweite Variable, nämlich “lightNormalIndex” zeigt normalerweise auf eine Tabelle mit Normal-Vektoren. Diese bräuchten wir um extra Lichteffekte auf und für das Model zu generieren. Zu bekommen ist diese Tabelle auf den Servern von ID Software. Für uns allerdings ist diese Variable vorerst noch uninteressant, da wir in diesem Tutorial nicht darauf eingehen werden.

C-/C++-Quelltext

1
2
3
4
5
6
struct MD2_Frame
   {
       float scale[3];
       float translate[3];
       char name[16];
   };

Wie ich schon sagte, ist ein Frame eine Animationsphase. Um genauer zu sein, eine Schlüsselstellung/-position. Um eine Animation abspielen zu können bräuchten wir von daher einen kompletten Durchlauf durch all diese Animationsphasen. Nunja, wie gesagt wollen wir uns in diesem Tutorial nicht damit beschäftigen. Deshalb brauchen die dies noch nicht zu wissen.
Die erste Variable, nämlich “scale” hat 3 Elemente, und, na? Wofür wohl? Na natürlich für die Vertexkoordinaten. Die Vertexkoordinaten werden später skaliert. Und da es 3 Achsenausrichtungen gibt (X, Y, Z), brauchen wir eine Skalierung, die uns all diese Achsen skaliert.
“translate” sieht da nicht anders aus. Und zwar brauchen wir wieder einen Translatierungswert, den wir später mit den X, Y, Z-Vertexkoordinaten addieren.
Die letzte Variable liest und speichert die Namen der Frames in sich. Während der Animierung mit dem Modellierungsprogramm legt man dabei fest, wie die verschiedenen “Keyframes” (engl.) heissen sollen. Standartmäßig benutzt man “Rennen1”, “Springen1”, “Hocken” etc. Wer ganz genau weiss, das jeder einzelne Name des Keyframes länger als 16 Zeichen beträgt (Sehr selten), kann die Elementzahl natürlich auf einen beliebigen Wert erhöhen. Für uns aber reicht es sowieso aus, da wir ja keine Animation abspielen wollen.

C-/C++-Quelltext

1
2
3
4
struct MD2_FrameData
   {
       MD2_Vertex* pVertices;
   };

Die vorige Struktur enthält nun keine Angaben zu den geometrischen Daten. Und da wir ja den ersten Frame zeichnen wollen, brauchen wir auch die entsprechenden Koordinaten. Dazu erstellen wir einen Zeiger auf die MD2_Vertex-Struktur.

C-/C++-Quelltext

1
2
3
4
5
   struct MD2_Triangle
   {
       short vertexIndices[3];
       short textureIndices[3];
   };

Beide short-Variablen zeigen auf den globalen Vertexspeicher. Dieser wiedrrum enthält die eigentlichen und richtigen Vertexdaten. Genau das selbe findet für die TexturInformationen statt.

C-/C++-Quelltext

1
2
3
4
struct MD2_Skin
   {
       char path[64];
   };

Die Variable “path” gibt an, wie sich schon vermuten lies (^_^), welchen Dateinamen die Textur hat. Über den wird die eigentliche Textur dann geladen.

C-/C++-Quelltext

1
2
3
4
 struct MD2_TexCoord
   {
       short s, t;
   };

Die 2 short-Variablen “s” und “t” speichern unsere Breite- und Höhenkoordinaten.

C-/C++-Quelltext

1
2
3
4
5
   struct MD2_GlCommandVertex
   {
       short s, t;
       int vertexIndex;
   };

Diese Struktur ist für uns erstmal uninteressant. Wir werden sie in diesem Tutorial noch nicht brauchen. Wir lesen sie trotzdem ein!

C-/C++-Quelltext

1
   GLuint SkinAdr;

Über “SkinAdr” werden wir die Textur laden, dass heisst wir erstellen neuen Speicher für die Textur.

C-/C++-Quelltext

1
   MD2_Header* m_pHeader;

Ein Zeiger auf den MD2_Header.

C-/C++-Quelltext

1
   MD2_Skin* m_pSkins;

Ein Zeiger auf MD2_Skin.

C-/C++-Quelltext

1
   MD2_Triangle* m_pTriangles;

Ein Zeiger auf MD2_Triangle.

C-/C++-Quelltext

1
   MD2_TexCoord* m_pTexCoords;

Ein Zeiger auf MD2_TexCoord.

C-/C++-Quelltext

1
   MD2_GlCommandVertex* m_pGLCmdVertices;

Ein Zeiger auf MD2_GlCommandVertex.

C-/C++-Quelltext

1
   MD2_Frame* m_pFrames;

Ein Zeiger auf MD2_Frame.

C-/C++-Quelltext

1
   MD2_FrameData* m_pFrameData;

Ein Zeiger auf MD2_FrameData.

C-/C++-Quelltext

1
};


Wir beenden die Klasse.

Der Konstruktor
So, bishierhin war es ja recht simpel, da wir erstmal alles deklariert haben. Nun wollen wir aber alle diese Instanzen “leeren”, damit in ihnen Platz für neue Werte zur Verfügung stehen. Mithilfe des Konstruktors (Wüsste auch keine bessere Alternative), leeren wir also alle Instanzen:

C-/C++-Quelltext

1
2
3
CMD2Model::CMD2Model()
{
    m_pHeader=NULL;

Wir leeren den Zeiger auf die MD2_Header-Struktur.

C-/C++-Quelltext

1
    m_pFrames=NULL;

Wir leeren den Zeiger auf die MD2_Frame-Struktur.

C-/C++-Quelltext

1
    m_pFrameData=NULL;

Wir leeren den Zeiger auf die MD2_FrameData-Struktur.

C-/C++-Quelltext

1
    m_pSkins=NULL;

Wir leeren den Zeiger auf die MD2_Skin-Struktur.

C-/C++-Quelltext

1
    m_pGLCmdVertices=NULL;

Wir leeeren den Zeiger auf die MD2_GlCommands-Struktur.

C-/C++-Quelltext

1
    m_pTriangles=NULL;

Wir leeren den Zeiger auf die MD2_Triangle-Struktur.

C-/C++-Quelltext

1
    m_pTexCoords=NULL;

Wir leeren den Zeiger auf die MD2_TexCoord-Struktur.

C-/C++-Quelltext

1
    SkinAdr=NULL;

Wir leeren den Speicher den wir für die Textur brauchen.

C-/C++-Quelltext

1
}


Der Destruktor
Wir beenden den Konstruktor und fahren mit dem Dekonstruktor fort. Im Dekonstruktor geben wir nun den Speicher jeder Instanz frei weil sie hier nicht mehr gebraucht werden.

C-/C++-Quelltext

1
2
3
4
CMD2Model::~CMD2Model()
{
    if(m_pHeader != NULL)
    {

Wenn MD2_Header einen Wert hat...

C-/C++-Quelltext

1
        delete[] m_pTriangles;

...lösche den Zeiger auf die MD2_Triangle-Struktur...

C-/C++-Quelltext

1
        delete[] m_pTexCoords;

...auf die MD2_TexCoord-Struktur...

C-/C++-Quelltext

1
        delete[] m_pGLCmdVertices;

…auf die MD2_GlCommand-Struktur...

C-/C++-Quelltext

1
        delete[] m_pSkins;

...und auf die MD2_Skin-Struktur.

C-/C++-Quelltext

1
2
    for(int nFrame=0; nFrame < m_pHeader->numFrames; nFrame++)
    {

Diese Schleife durchläuft die kompletten Frames...

C-/C++-Quelltext

1
        delete[] m_pFrameData[nFrame].pVertices;

….und löscht anschliessend alle Instanzen die auf die Vertexkoordinaten zeigen.

C-/C++-Quelltext

1
2
3
    }

        delete[] m_pHeader;

Anschliessend löschen wir den kompletten Header. Wichtig ist das wir ihn erst hier komplett freigeben zw. Löschen, da wir ein paar Zeilen über uns noch ein paar andere Dinge löschen, die damit zusammenhängen.

C-/C++-Quelltext

1
    m_pHeader=NULL;

Wir löschen den restlichen Inhalt vom Zeiger auf den MD2_Header.

C-/C++-Quelltext

1
    m_pTriangles=NULL;

Hier das gleiche mit dem Zeiger auf die MD2_Triangle-Struktur.

C-/C++-Quelltext

1
    m_pTexCoords=NULL;

Hier auf mit dem Zeiger auf die MD2_TexCoord-Strukur.

C-/C++-Quelltext

1
    m_pFrames=NULL;

Dasselbe mit dem Zeiger auf die MD2_Frame-Strukrur.

C-/C++-Quelltext

1
    m_pFrameData=NULL;

Genau das gleiche mit dem Zeiger auf MD2_FrameData.

C-/C++-Quelltext

1
    m_pGLCmdVertices=NULL;

Und hier mit dem Zeiger auf die MD2_GlCommand-Struktur.

C-/C++-Quelltext

1
2
    }
}


Das Model laden und die Instanzen füllen
So, die Instanzen und Methoden wurden nun deklariert und geleernt, sodass wir anfangen können, sie zu füllen. Dazu springen wir jeweils zu einem Offset und lesen ab da den jeweiligen Block ein. Für fast jede Instanz bzw Struktur gibt es ein Offset um den jeweiligen Block einlesen zu können.

C-/C++-Quelltext

1
2
3
bool CMD2Model::LoadModel(const char* szFilename)
{
  FILE* ModelFile=NULL;

Wir rufen die LoadModel-Methode auf um hier drin alle Werte einzulesen und zuzuweisen. Da wir mit "fstream" arbeiten, ist es sinnvoll den Zeiger nach der Zuweisung zu leeren. "ModelFile" wird unsere Model laden.

C-/C++-Quelltext

1
2
3
4
if(!(ModelFile=fopen(szFilename,"rb")))
    {
        MessageBox(NULL,"!Modelfile=fopen(szFilename,rb) ","Modelloader",MB_OK);
    }

Wir laden das File und prüfen gleichzeitig, ob es irgendeinen Fehler gab. Falls es einen geben sollte, wird eine MessageBox ausgegeben.

C-/C++-Quelltext

1
m_pHeader = new MD2_Header;

Wir reservieren den Speicher für m_pHeader vom Typ "MD2_Header". Wir geben dem Zeiger auf "MD2_Header" also einen Wert.

C-/C++-Quelltext

1
fread(m_pHeader,sizeof(MD2_Header),1, ModelFile);

Nachdem wir den Speicher für den Zeiger auf "MD2_Header" bereitgestellt haben, fangen wir nun an, den Header einzulesen.

C-/C++-Quelltext

1
m_pTriangles = new MD2_Triangle[m_pHeader->numTriangles];

Hier im Prinzip dasselbe wie beim Header, nur das wir den Speicher für m_pTriangles vom Typ "MD2_Triangle" reservieren. Gleichzeitig übergeben wir eun neues Element an den Zeiger, nämlich "m_pHeader->numTriangles (Anzahl der Dreiecke)".

C-/C++-Quelltext

1
m_pTexCoords = new MD2_TexCoord[m_pHeader->numTexCoords];

Wir stellen m_pTexCoords neuen Speicher vom Typ "MD2_TexCoord" zur verfügung und übergeben gleichzeitig ein neues Element mit den Texturkoordinaten.

C-/C++-Quelltext

1
m_pGLCmdVertices = new MD2_GlCommandVertex[m_pHeader->numGlCommands];

Wieder reservieren wir neuen Speicher für den Zeiger auf "MD2_GlCommandVertex". Dazu dann wieder das Element "m_pHeader->numGlCommands".

C-/C++-Quelltext

1
m_pSkins = new MD2_Skin[m_pHeader->numSkins];

m_pSkins wird Speicher reserviert und bekommt auch ein neues Element zugewiesen.

C-/C++-Quelltext

1
m_pFrames = new MD2_Frame[m_pHeader->numFrames];

Hier genau dasselbe, dem Zeiger auf "MD2_Frame" wird neuer Speicher vom Typ "MD2_Frame" reserviert. Außerdem bekommt der Zeiger ein neues Element mit der Anzahl der Frames.

C-/C++-Quelltext

1
 m_pFrameData = new MD2_FrameData[m_pHeader->numFrames];

Hier direkt dasselbige wie bei “m_pFrames”, nur das hier der Speicher für m_pFraneData reserviert wird.

So, das hätten wir schonmal geschafft. Jeder Zeiger hat den Inhalt der Struktur bekommen und die verschiedenen Instanzen wurden dem Zeiger auch zugewiesen. Nun müssen aber zu den einzelnen Offsets springen und die einzelnen Strukturen füllen. Los geht’s!

C-/C++-Quelltext

1
 fseek(ModelFile, m_pHeader->offsetFrames, SEEK_SET);

Wir springen zum Offset der Frames. Sind wir ersteinmal hier her gesprungen, können wir mit dem einlesen der Struktur fortfahren.

C-/C++-Quelltext

1
2
for(int i=0; i < m_pHeader->numFrames; i++)
    {

Wir legen eine Variable an, die die Anzahl der Frames speichert. Über diese Variable “i” ist es uns hinterher möglich, durch alle Frames zu “loopen”.

C-/C++-Quelltext

1
fread(&m_pFrames[i],sizeof(MD2_Frame),1,ModelFile);

Wir fangen an, die Frames einzulesen. Wenn ihr euch erinnern könnt, haben wir nach dem reservieren des Speichers ein neues Element angelegt und zwar “[m_pHeader->numFrames]”. Da wir jetzt diesen Wert “geloopt” haben, können wir ihn einfach als Element übergeben.

C-/C++-Quelltext

1
m_pFrameData[i].pVertices = new MD2_Vertex[m_pHeader->numVertices];

Wie ihr wisst, haben wir “m_pFrameData” vorhin neuen Speicher vom Typ “MD2_FrameData” reserviert. Da wir über den Zeiger auf die “MD2_Vertex”-Struktur zugreifen, müssen wir neuen Speicher über den Zeiger auf die eigentliche Struktur reservieren. Und zwar vom Typ “MD2_Vertex”. Außerdem weisen wir “m_pFrameData[m_pHeader->numFrames].pVertices” jetzt noch ein neues Element zu. Und zwar “m_pHeader->numVertices”.

C-/C++-Quelltext

1
fread( m_pFrameData[i].pVertices, sizeof( MD2_Vertex ), m_pHeader->numVertices, ModelFile );

Wir lesen die kompletten m_pFrameData-Daten ein. Mit einem “sizeof(MD2_Vertex)” da wir ja immernoch einen Zeiger in der MD2_FrameData-Struktur haben.

C-/C++-Quelltext

1
}

Wir beenden die Schleife nachdem wir alles erfolgreich eingelesen haben.

Soweit so gut, die Frames wurden geladen und der MD2_Header wurde eingelesen. Doch, fehlt da nicht noch was? (:
Ganz genau, die restlichen Strukturen.

C-/C++-Quelltext

1
fseek(ModelFile, m_pHeader->offsetSkins, SEEK_SET);

Wir springen wieder zum Offset der Skins.

C-/C++-Quelltext

1
fread(m_pSkins, sizeof(MD2_Skin), m_pHeader->numSkins, ModelFile);

Nachdem wir zu dem Offset gesprungen sind, können wir die Skin-Struktur einlesen.

C-/C++-Quelltext

1
fseek(ModelFile, m_pHeader->offsetTexCoords, SEEK_SET);

Wir springen zu dem Texturkoordinaten-Offset.

C-/C++-Quelltext

1
fread(m_pTexCoords, sizeof(MD2_TexCoord), m_pHeader->numTexCoords, ModelFile);

Wir lesen die Texturkoordinaten ein.

C-/C++-Quelltext

1
fseek(ModelFile, m_pHeader->offsetTriangles, SEEK_SET);

Wir springen wieder zu dem Offset der Triangles.

C-/C++-Quelltext

1
fread(m_pTriangles, sizeof(MD2_Triangle), m_pHeader->numTriangles, ModelFile);

Wir lesen die Dreiecke ein.

C-/C++-Quelltext

1
2
3
4
    fclose(ModelFile);

    return true;
};

Zu guter letzt schliessen wir die Datei und geben ein “true” an die Methode zurück.

Die Modeldaten errechnen und das Model rendern
Nun ist es soweit, wir haben den Strukturen ihre Werte zugewiesen. Jetzt können wir richtig loslegen. Damit das Model gerendert werden kann, müssen wir erst ein paar Umrechnungen vornehmen. Wie schon erwähnt, werden wahrscheinlich aus Platzgründen, die Modeldaten auf jeweils 1 Byte geschrumpft. Unsere Aufgabe besteht nun darin, die Daten wieder umzuwandeln, damit wie sie an die OpenGL-Befehle übergeben können. Ich möchte kurz erklären, wie man die Modeldaten “entschrumpft” (tolles Wort :D). Nun, um an die eigentlichen Vertexdaten zu gelangen, müssen wir den Skalierungswert mit den Vertexdaten multiplizieren und mit dem Translatierungswert addieren. Danach ist es uns dann ermöglicht, die richtigen Daten an OpenGL weiterzuschicken, damit er uns unser Model rendert. Also, auf geht’s!

C-/C++-Quelltext

1
2
3
4
void CMD2Model::DrawMD2Model()
{
    int nVertexNr = 0;
    int nTexCoordNr = 0;

Die beiden Variablen brauchen wir um später die Triangledaten aus dem 1. Frame zu holen.

C-/C++-Quelltext

1
float nX ,nY, nZ;

Wir deklarieren 3 float-Variablen. Diese 3 Variablen werden später den Wert der richtigen Vertexdaten übernehmen (X, Y, Z).

C-/C++-Quelltext

1
2
    double dSkinResX=0;
    double dSkinResY=0;

Wir legen zwei neue double-Variablen an, sie werden später die Werte der Texturgröße (X, Y) übernehmen.

C-/C++-Quelltext

1
2
dSkinResX = (double)m_pHeader->skinWidth;
dSkinResY = (double)m_pHeader->skinHeight;

Die zuvor deklarierten Variablen bekommen jetzt einen neuen Wert. Damit skinWidth und skinHeight dann an die double-Variablen übergeben werden können, müssen wir die 2 Integers vorher casten. Das machen wir mit “(double)”. Eine Alternative würde so lauten:
static_cast <double>(m_pHeader->skinWidth/Height);

C-/C++-Quelltext

1
glEnable(GL_TEXTURE_2D);

Wir “erlauben”, dass OpenGL die Texturen anzeigen darf.

C-/C++-Quelltext

1
glBindTexture(GL_TEXTURE_2D, SkinAdr);

Wir binden “SkinAdr” an das nächste Model (SkinAdr hat die Textur gespeichert).

C-/C++-Quelltext

1
glBegin(GL_TRIANGLES);

Wir teilen OpenGL mit, dass es sich bei den nachfolgenden Vertexdaten um eine Dreiecks-Konstruktion handelt.

C-/C++-Quelltext

1
2
for(int nTriangle=0; nTriangle < m_pHeader->numTriangles; nTriangle++)
{

Wir “loopen” durch uns durch alle Traingles.

C-/C++-Quelltext

1
2
for(int nVertex=0; nVertex < 3; nVertex++)
{

Durch alle 3 Vertices des Dreiecks “loopen”.

C-/C++-Quelltext

1
nVertexNr = m_pTriangles[nTriangle].vertexIndices[nVertex];

Hier besorgen wir uns den ersten Frame, da wir ja keine Animation wollen.

C-/C++-Quelltext

1
nTexCoordNr = m_pTriangles[nTriangle].textureIndices[nVertex];

Hier besorgen wir uns die Texturkoordinaten.

C-/C++-Quelltext

1
nX = (float)m_pFrameData[0].pVertices[nVertexNr].vertex[0];

Hier casten wir die noch “geschrumpften” Vertices und übergeben sie an “nX”.

C-/C++-Quelltext

1
nX = (nX * m_pFrames[0].scale[0]) + m_pFrames[0].translate[0];

Ja, jetzt wird’s interessant. Vorhin haben wir noch die Vertexdaten in “nX” gespeichert. Nun müssen wir nX mit dem Skalierungswert skalieren und mit seinem Translatierungswert addieren. Und das war's schon! So einfach ist das! (:

C-/C++-Quelltext

1
nY = (float)m_pFrameData[0].pVertices[nVertexNr].vertex[1];

Wir übergeben die Y-Vertexdaten an “nY” und casten die noch “geschrumpften” Daten nach float.

C-/C++-Quelltext

1
nY = (nY * m_pFrames[0].scale[1]) + m_pFrames[0].translate[1];

Wir multiplizieren die Vertexdaten mit dem Skalierungswert und addieren dazu den Translatierungswert.

C-/C++-Quelltext

1
nZ = (float)m_pFrameData[0].pVertices[nVertexNr].vertex[2];

Wir übergeben die Z-Vertexdaten an “nZ” und casten die Daten nach float.

C-/C++-Quelltext

1
nZ = (nZ * m_pFrames[0].scale[2]) + m_pFrames[0].translate[2];

Wir multiplizieren den Skalierungswert wieder mit den Vertexdaten und addieren darauf die Translatierungswerte.

So, dass wars dann im Prinzip auch schon. Das wichtigste haben wir hinter uns. Wir haben bis jetzt die Modeldaten “entschrumpft” und uns die Texturkoordinaten besorgt. Doch apropós Texturkoordinaten, da fehtl doch noch was (:.
Und zwar knnen die TexturInformationen immernoch nicht so übergeben werden. Um das Problem zu lösen dividieren wir die Texturkoordinaten durch die Texturgröße. Damit hätten wir's dann auch schon fast.

C-/C++-Quelltext

1
     double dU = (double)m_pTexCoords[nTexCoordNr].s / dSkinResX;

Wir dividieren die Texturkoordinaten durch die Texturgröße um die richtigen Werte zu bekommen. Dabei casten wir die ganze Geschichte nochmal nach “double”.

C-/C++-Quelltext

1
     double dV = (double)m_pTexCoords[nTexCoordNr].t / dSkinResY;

Auch hier dividieren wir die Texturkoordinaten durch die Texturgröße und casten den Kram.

C-/C++-Quelltext

1
double dScaleModel = 1.0f;

Wir legen eine double-Variable an, die nahcher unsere kompletten Vertexdaten (X, Y, Z) skalieren soll.

C-/C++-Quelltext

1
glTexCoord2d(dU, dV);

Wir senden OpenGL zu, dass er die fertiggecasteten und dividierten Werte auf das nachfolgende Objekt rendern soll.

C-/C++-Quelltext

1
glVertex3d(nX * dScaleModel, nY * dScaleModel, nZ * dScaleModel);

Zu guter letzt sagen wir OpenGL noch, dass die Daten und deren Skalierung gerendert werden sollen.

C-/C++-Quelltext

1
2
3
4
5
6
        }
    }

    glEnd();
    
};

Nachdem wir den Modelloader nun fertiggeschrieben haben, möchte ich euch noch auf Kleinigkeiten hinweisen, die von IDE zu IDE unterschiedlich sein können:
Und zwar hatte ich lange Zeit das Problem, dass Visual Studio jede Struktur von allein Optimiert hat. Die Lösung: Einfach unter den Projekteigenschaften->C++->Codegenerierung-> die Ausrichtung der Strukturmember von >2 Byte auf 1 setzen.

Einen anderen Punkt den ich noch ansprechen wollte, war das Modellieren. Es gibt viele verschiedene Modellierungsprogramme (3ds Max, Cinema 4D, Blender...etc.). Ich bevorzuge dabei Cinema 4D und Blender. Zum einen sind diese beiden Modellierungsprogramme noch recht einfach zu Hand haben und außerdem unterstützen beide -na?- das MD2-Format. Bei der Exportierung ins MD2-Format sollte man folgendes beachten:
-den genauen Path eingeben und (sehr wichtig!!!), dass man anstatt “untitled.blender” “untitled.md2” machen muss, da sonst trotzdem ins .BLEND-Format exportiert wird.
-vor dem exportieren fragt Blender Dich, ob die Meshdaten transformiert werden sollen, natürlich “Nein” drücken, da das Model dann eher in Richtung Abstrackt endet ;-D
Ein ziemlich standfestes Portierungstool ist auch Milkshape 3D. Es unterstützt sämtliche Modelformate und lässt natürlich von jedem unterstützen Format ins MD2-Format exportieren.

Ansonsten hoffe ich euch mit diesem recht ausführlichen Tutorial etwas geholfen zu haben. Ich freue mich schon auf's nächste mal! ;-)

Wenn ihr alles richtig gemacht habt, siehts dann so aus:

(Link)


(Link)


Demnächst werdet ihr mich unter www.Coders-Square.de finden ;-).

Danke im Voraus, ich hoffe dieses Tutorial kommt zu den Tutorials und hilft irgendjemandem dadraußen.

Anonymous

unregistriert

2

30.11.2008, 00:19

Wua, das war lang. Ist es euch Wert das es zu den Artikel transferiert wird...? :idea:

Ahjau und bitte sagt vorher noch bescheid was ihr nicht so gut oder als falsch empfindet.

Danke im Voraus!

drakon

Supermoderator

Beiträge: 6 513

Wohnort: Schweiz

Beruf: Entrepreneur

  • Private Nachricht senden

3

30.11.2008, 03:03

Also hier ist bestimmt was mit der Formattierung falsch gelaufen..

Zitat

C-/C++-Quelltext

1
2
3
4
5
6
7
for(int nTriangle=0; nTriangle < m_pHeader->numTriangles; nTriangle++)
{
[cpp]
Wir “loopen” durch uns durch alle Traingles.
[cpp]
for(int nVertex=0; nVertex < 3; nVertex++)
{

Durch alle 3 Vertices des Dreiecks “loopen”.

C-/C++-Quelltext

1
nVertexNr = m_pTriangles[nTriangle].vertexIndices[nVertex];

Hier besorgen wir uns den ersten Frame, da wir ja keine Animation wollen.
[/cpp]
nTexCoordNr = m_pTriangles[nTriangle].textureIndices[nVertex]; [/cpp]


Über NULL vs. 0 kann man sich streiten, sowie auch um die ungarische Notation. ( Ich persönlich bevorzuge 0 und keine ungarische Notation, mal abgesehen vom m_)

Was aber definitv rein muss, ist, dass du die Initialisierungsliste benutzt:

C-/C++-Quelltext

1
2
3
4
5
CMD2Model::CMD2Model()
:
m_pHeader ( NULL )...
{
}


Dann fände ich es besser, wenn du mit C++ - Files arbeiten würdest.

Was aber wieder extrem Schade ist, ist, dass du dich auf eine API beschränkst, was ja überhaupt nichts mit dem Format zu tun hat.

Zitat

C-/C++-Quelltext

1
MessageBox(NULL,"!Modelfile=fopen(szFilename,rb) ","Modelloader",MB_OK);


Ich habe jetzt nicht alles durchgelesen, aber sieht ansonsten recht anständig aus. (Vlt. wäre ein kleiner Screenshot von einem Objekt, oder so interessant, den du so geladen hast.)

Anonymous

unregistriert

4

30.11.2008, 09:28

Hallo,
erstmal danke für deine Antwort. Jo, ich fixe die Codestellen schnell.

Zitat

Über NULL vs. 0 kann man sich streiten

Richtig.

Zitat

Was aber wieder extrem Schade ist, ist, dass du dich auf eine API beschränkst, was ja überhaupt nichts mit dem Format zu tun hat.

Da hast du Recht, ich überlege's mir nochmal ob ich ein komplett Platformübergreifendes Tutorials schreibe (Ist im Prinzip ja auch nicht sooo viel).

drakon

Supermoderator

Beiträge: 6 513

Wohnort: Schweiz

Beruf: Entrepreneur

  • Private Nachricht senden

5

30.11.2008, 09:43

Zitat von »"Coders-Square"«


Zitat

Was aber wieder extrem Schade ist, ist, dass du dich auf eine API beschränkst, was ja überhaupt nichts mit dem Format zu tun hat.

Da hast du Recht, ich überlege's mir nochmal ob ich ein komplett Platformübergreifendes Tutorials schreibe (Ist im Prinzip ja auch nicht sooo viel).


Jo. Eigene Typdefs und auf die (unnötigen) Aufrufe von Funktionen verzichten.
Mir ging es jetzt weniger darum,dass du da im Objekt drin auch noch OpenGL benutzt (läuft ja auf jeder Plattform), sondern eher, dass du dich da schon mal (wegen Kleinigkeiten) auf Windows einschränkst, was gar nicht nötig ist.

Anonymous

unregistriert

6

30.11.2008, 10:03

Ganz genau, es ist egal womit man es später rendert. Sind ja auch nur 2 Zeilen.

Eine Alternative zu dem Ladecode wäre CStudioFile oder fstream gewesen... ;)

Faule Socke

Community-Fossil

Beiträge: 1 915

Wohnort: Schreibtischstuhl

  • Private Nachricht senden

7

30.11.2008, 14:58

Was mir aufgefallen ist:
:arrow: Du verwendest keine Initialisierungslisten
:arrow: Es heist Destruktor, nicht Dekonstruktor ;-)
:arrow: Das Format ist Offensichtlich für Little-Edian Maschinen ausgelegt. Somit sind alle "Multi-Byte-Zahlen" (ints, floats, etc) "verdreht" in der Datei. Wenn du sie nun Byte für Byte lädst, wirdst du auf einer Little-Endian Maschine das korrekte Ergebnis bekommen. Auf einer Big-Endian wird nur totaler murks rauskommen, denn diese interpretiert die Zahlen anders. Auch wenn man das Format wohl nur auf einer x86 Maschine (little endian) verwenden wird, sollte es doch zumindest erwähnt werden, dass man eventuell convertirungsfunktionen schreiben muss.

So noch n bisschen was Positives: Das Tutorial ist nicht schlecht, die Beschränkung auf ogl stört auch nicht wirklich, du hättest es allerdings auch ohne Grafik-API machen können, das hätte der User dann selbst übernehnen müssen und du hättest es als "Fortgeschritten" klassifizieren können, weil Stupides Abschreiben nicht mehr zu einem Ergebnis führen würde ;-)

Alles in allem aber doch recht gut, es hätte aber auch gereicht, wenn du das was du jeweils über deinen 1-Zeilen-CPP-Tags hast als kommentar in sie rein schreibst und alles in einen Packst.

Socke

Anonymous

unregistriert

8

30.11.2008, 15:20

Zitat

Das Format ist Offensichtlich für Little-Edian Maschinen ausgelegt. Somit sind alle "Multi-Byte-Zahlen" (ints, floats, etc) "verdreht" in der Datei.

Verdreht ist meines Erachtens nicht ganz richtig...beim Little-Endian wird das Byte mit den minimalisiertesten und kleinsten Bits an der kleinsten Speicheradresse besetzt. Der Vorteil vom Little-Endian ist der, dass bei der Konvertierung von 2 Byte Zahlen zu den 4 Byte Zahlen nur 2 Nullbytes am Ende angesteckt werden. Ansonsten hast du vollkommen recht.
Eine Erwähnung, dass der Little-Endian bei dem Gebrauch auf einem Big-Endian konvertiert werden muss, ist es Wert ;-).

Danke!

Faule Socke

Community-Fossil

Beiträge: 1 915

Wohnort: Schreibtischstuhl

  • Private Nachricht senden

9

30.11.2008, 22:37

Ja ich weiss was Little endian ist. Die Bytes sind in "verdrehter" reihenfolge ;-). Das kommt natürlich auch drauf an, was man als richtig herum ansieht.

Socke

Anonymous

unregistriert

10

30.11.2008, 22:43

Jo klar, will ich ja garnicht in Frage stellen ;-).
Naja, Artikel gibt es auch schon, danke fkrauthan!
http://sppro.fkrauthan.de/2008/11/30/c-o…d2-modelloader/

Werbeanzeige