OpenGL -10.díl Náčítání modelů ze souborů - Builder.cz - Informacni server o programovani

Odběr fotomagazínu

Fotografický magazín "iZIN IDIF" každý týden ve Vašem e-mailu.
Co nového ve světě fotografie!

 

Zadejte Vaši e-mailovou adresu:

Kamarád fotí rád?

Přihlas ho k odběru fotomagazínu!

 

Zadejte e-mailovou adresu kamaráda:

Soutěž

Sponzorem soutěže je:

IDIF

 

Kde se koná výstava fotografií Luďka Vojtěchovského?

V dnešní soutěži hrajeme o:



C/C++

OpenGL -10.díl Náčítání modelů ze souborů

open gl

5. září 2002, 00.00 | Po dlouhé době přicházíme s dalším dílem seriálu. Tentokrát se budeme věnovat načítání modelů ze souborů ASE. Půjde o seznámení s jednoduchým ASCII formátem, do kterého můžete z 3D MAXU exportovat své modely.

{Teorie}

Po dlouhé době přicházím s dalším dílem seriálu, který jsem napsal při dlouhém prázdninovém nicnedělání. Budeme se věnovat načítání modelů ze souborů ASE. Půjde o seznámení s jednoduchým ASCII formátem, do kterého můžete z 3D MAXU exportovat své modely. Připravte se, nebude to žádná sranda, ale tvrdá programátorská řehole. Výsledek ale bude stát za to.

Modely můžeme ukládat do nejrůznějších formátů. Mezi nejčastější formáty patří DXF (původně jsem zamýšlel tento článek napsat o něm), 3DS (bude popsán v některém dalším díle), MAX, DWG, ASE atd. Formáty rozdělujeme podle toho, jestli jsou data zapisovány binárně nebo textově.

Pro tento článek jsem vybral ASCII formát ASE, který vyexportujeme v programu 3D Max. Tento formát není nic moc, ale nějak začít musíme. Ale pro nás má jednu velkou přednost, jednoduchost. Nevýhodou naopak je pomalost zpracování textového souboru.

Napišme si třídu, která bude ASE soubory načítat. Tuto třídu pojmenujeme TASEFile. Do této třídy budeme natahovat informace o počtech objektů, počtech materiálů, ukazatel na stream, pak budeme potřebovat data jednotlivých objektů a data materiálů.

Popišme si nejdříve jak jsou data v souboru organizována. Vysvětluje to následující obrázek:

Při načítání si nejdříve zjistíme základní informace, jako je počet objektů, počet materiálů atd. Potom načteme materiály. Každý materiál je reprezentován barvou a popřípadě i texturou (velmi zjednodušeně řečeno). Celá scéna se skládá z jednoho nebo více objektů. Proto se dáme do načítání jednotlivých objektů. Každý objekt má definované umístění a pootočení (neuvažoval jsem je). Pro každý objekt načteme všechny vrcholy objektu (VERTEX). Potom načteme, jakým způsobem tyto vrcholy pospojovat (FACE). Pokud je objekt potexturován načteme navíc vrcholy pro texturu TVERTEX a pro namapování potřebujeme také TFACE. Navíc ještě můžeme ze souboru načíst nebo vypočíst normály, aby objekt vypadal reálněji ve světle. Pokud jste ještě nepochopili, co jsou vertexy a face, podívejte se na následující obrázek:

Pro dokonalé pospojování všech vrcholů nám postačí spojovat vrcholy po 3 bodech (například A,B,F atd).

Udělejme si ještě krátký výlet do souboru ASE, ať si můžeme představit co máme před sebou:

*3DSMAX_ASCIIEXPORT 200  
 *COMMENT "AsciiExport Version 2,00 - Sun Feb 03 12:56:39 2002"
*SCENE {
*SCENE_FILENAME "lod01-finsh.max" *SCENE_FIRSTFRAME 0 *SCENE_LASTFRAME 100 *SCENE_FRAMESPEED 30 *SCENE_TICKSPERFRAME 160 *SCENE_BACKGROUND_STATIC 0.0000000000 0.0000000000 0.0000000000 - barva podkladu *SCENE_AMBIENT_STATIC 0.0431400016 0.0431400016 0.0431400016 } *MATERIAL_LIST { - seznam materiálů a submateriálů a jejich vlastnosti . . . . . . . atd. . . . . . . . . . . . . } *GEOMOBJECT { - popis jednoho objektu . . . . . . . atd. . . . . . . . . . . . . } *MESH { *TIMEVALUE 0 *MESH_NUMVERTEX 8 - počet vertexů objektu *MESH_NUMFACES 12 - počet face objektu *MESH_VERTEX_LIST { *MESH_VERTEX 0 -0.1468413621 1.2552357912 -0.3109439015 - takhle vypadá popis jednoho vertexu vrcholu - co jednotlivé čísla znamenají zleva: pořadové číslo, souřadnice x, y, z . . . . . . . . . . . . . . . . . . . } *MESH_FACE_LIST { *MESH_FACE 0: A: 0 B: 2 C: 3 AB: 1 BC: 1 CA: 0 *MESH_SMOOTHING 2 *MESH_MTLID 1 - popis FACE - opět první číslo je pořadové číslo, dále nás zajímají hodnoty u A, B a C - říká které vertexy se mají spojit, čísla za A,B, C jsou pořadové čísla vertexů . . . . . . . . . . . . . . . . . . . } *MESH_NUMTVERTEX 0 - počet texturových vertexů je 0, takže bez textury *PROP_MOTIONBLUR 0 *PROP_CASTSHADOW 1 *PROP_RECVSHADOW 1 *WIREFRAME_COLOR 1.0000000000 0.6627451181 0.0000000000 }

To je vystřižená jen malá část ASE souboru a našim úkolem je správně využít data, která se nám tu povalují. Komentář uvnitř rámečku je natolik bohatý, že není třeba dál jednotlivé řádky rozebírat. A jakým způsobem budeme data načítat se dozvíte v další části článku.

[-more-]{Programování}

To by byla teorie a pusťme se do programování. Jak již bývá zvykem, algoritmy a postup programování jsem si sám nevycucal z prstu (a proč taky), ale inspiroval jsem se výtečnými tutoriály na www.gametutorials.com. Zde je jeden tutoriál věnovaný právě čtení souborů ASE. Všechny tutoriály na zmíněné adrese jsou napsány pro Visual C++ a já jsem je přežvýkal po svém, polidštil a takříkajíc počeštil :). Zkrátka, jak říkám celý kód z GAMETUTORIALS jsem kompletně přepracoval do C++ Builderu a mohu říct, že z původního kódu nezůstal kámen na kameni. Tak se na něj podívejme. Pro dokonalé pochopení navíc doporučuji nahlížet do souboru ASE.

V hlavičkovém souboru ASEClass.h máme všechno potřebné důležité třídy, datové struktury a konstanty. Na začátku tohoto souboru si všimněte právě definic konstant (např. #define OBJECT "*GEOMOBJECT" ). Podle těchto konstant budeme vyhledávat (a orientovat se) v souboru ASE. Dále zde máme definovány samozřejmosti jako 2D a 3D bod ve strukturách TBod2D a TBod3D. Stejně tak se zde nachází definice 1 FACE, který se skládá ze 3 celočíselných hodnot (viz výše). A poslední strukturou je TMyMaterial, která má za úkol popisovat materiál objektu.

Veškerou hybnou silou celého načítacího procesu jsou dvě třídy
T3DObject
TASEFile
Podívejme se na ně blíže:

class T3DObject { 
private:
public:
T3DObject();
~T3DObject();
TList *Vertexy;  // všechny vertexy objektu
TList *FaceList; 
// všechny face objektu
TList *TexVertexy; // všechny texturové vertexy objektu
TList *TexFaceList; // všechny face objektu
TBod3D *Bod;
TBod2D *Bod1;
TFace3D *Face;
int numvertex; // počet vertexu
int numface; // počet face
int MatID;  // identifikační číslo použitého materiálu
};
class TASEFile {
private:
public:
int pocetobj; // celkový počet objektů
int pocetmat; // celkový počet materiálů 
FILE *fr;   // Stream
TASEFile();
~TASEFile();
TMyMaterial *Material;
TList *Materialy;  // všechny materiály
 TList *Objekty; // všechny objekty
T3DObject *Object; 
void MoveToObject (int desiredObject);
int LoadFromFile(char soubor[]);
void Render();
};
             

V obou třídách je velmi důležitým prvkem nevizuální objekt TList, který s oblibou využívám pro uložení jakýchkoli dat. Pro připomenutí pouze uvedu, že TList je analogií známého TStringListu, ale místo položek (Item) typu AnsiString jsou zde ukládány položky typu void, takže do něj můžeme nacpat úplně vše. Do objektu TASEFile, uložíme veškeré informace o materiálech a hlavně všechny objekty.

O načítání ze souboru se stará funkce LoadFromFile a zobrazení zajišťuje funkce Render. Funkce MoveToObject je pomocná funkce, která přesunuje ukazatel ve streamu před určitý objekt. Nyní se podívejme na některé záludnosti načítací funkce, na první pohled se funkce může zdát nepřehledná, ale opakuje se zde několikrát jedna a tatáž myšlenka:

MoveToObject(objekt);  // posunout ukazatel ve streamu na určitý objekt
while (!feof(fr)) // čtení do konce souboru
{
fscanf(fr, "%s", &strWord); // načte jeden řetězec
if (!strcmp(strWord, "*MESH_FACE_LIST")) // pro urychlení načítání, 
aby se nečetlo do konce souboru
 break;
if (!strcmp(strWord, VERTEX))  // hledám vertexy
{
fscanf(fr, "%d", &poradi); // nedůležité, číselné označení vertexu
fscanf(fr, "%f %f %f", &x, &y, &z); // hodnota vertexu x,y,z
Object->Bod = new TBod3D; // vytvořím objekt bod3D
Object->Bod->x = x;
Object->Bod->y = y;
Object->Bod->z = z;

Object->Vertexy->Add((void *) Object->Bod); // načtený bod přidám k vertexům } else fgets(strWord, 100, fr); // zahodit prázdné řetězce }

Veškeré načítání je postaveno na tomto principu, najít data v souboru (v tomto případě vertexy), načíst tyto data a vložit je do paměti (do TListu Vertexy). Doufám, že vám tato malá ukázka postačí k pochopení, protože pro podrobnější popis funkce zde nemáme prostor. Podívejme se nyní na to, jakým způsobem využít načtená data a zobrazit je. To řeší funkce Render. Připomeňme, že

TBod3D *Bod;
TBod2D *TexBod;
TFace3D *Face;
TFace3D *TexFace;


glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); glEnable(GL_TEXTURE_2D); glTranslatef(0.0f,0.0f,-20.0f); // posun natvrdo glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glRotatef(0, 1, 0, 0); glRotatef(rot++, 0, -1, 0);
for (int c = 0; c < Objekty->Count; c++) { // procházíme všechny načtené objekty Object = (T3DObject *)Objekty->Items[c]; if (Object->MatID != 314) { // pokud má objekt texturu tak MatID != 314 glBindTexture(GL_TEXTURE_2D, Object->MatID); Material = (TMyMaterial *) Materialy->Items[Object->MatID]; for (int i = 0; i < Object->FaceList->Count; i++) { // projít všechny face objektu if (Material->active == true) { // zobrazení s texturou
Face = (TFace3D *) Object->FaceList->Items[i]; // přístup k face
TexFace = (TFace3D *) Object->TexFaceList->Items[i];
// přístup k TextFace
  glBegin(GL_TRIANGLES);
  Bod = (TBod3D *)Object->Vertexy->Items[Face->a];
  TexBod = (TBod2D *)Object->TexVertexy->Items[TexFace->a];
  glTexCoord2f(TexBod->x, TexBod->y);
  glVertex3f(Bod->x, Bod->y, Bod->z);
  Bod = (TBod3D *) Object->Vertexy->Items[Face->b];
  TexBod = (TBod2D *)Object->TexVertexy->Items[TexFace->b];
  glTexCoord2f(TexBod->x, TexBod->y);
  glVertex3f(Bod->x, Bod->y, Bod->z);
  Bod = (TBod3D *)Object->Vertexy->Items[Face->c];
  TexBod = (TBod2D *)Object->TexVertexy->Items[TexFace->c];
  glTexCoord2f(TexBod->x, TexBod->y);
  glVertex3f(Bod->x, Bod->y, Bod->z);
  glEnd();
} 
else {
. . . . zobrazení bez textury . . . .
} } } }
 

V cyklu procházíme uložené 3D objekty, máme je uloženy v TListu Objekty. Přístup k jednotlivým 3D objektům dostaneme přes
Object = (T3DObject *)Objekty->Items[c];
Ukazatel z položky TListu je typu void *, proto jej musíme přetypovat na vhodný typ (T3DObject *). Stejně získáme přístup k materiálům. Nyní máme ukazatel na jeden objekt a přes něj se přistupujeme k jednotlivým vertexům a face (opět uloženy v TList). No a nakonec stačí vykreslit trojúhelník a správně namapovat texturu. Myslím, že to již nepotřebuje větší komentář.

Poznámky:
V textu se vyskytuje výraz face, pro který jsem nenašel český ekvivalent. Face nám říká, které 3 vertexy máme spojit.

Zdrojové soubory:
Moje řešení (BCB 4.0 500 KB)
GameTutorials (VC++6.0 250 KB)

V uvedeném příkladu jsem neřešil všechny možnosti formátu ASE. Neřešil jsem umístění celého modelu v prostoru včetně otočení, dále jsem neřešil normály atd. To už je výzva pro vás, co myslíte?

Obsah seriálu (více o seriálu):

Tématické zařazení:

 » Rubriky  » C/C++  

 

 

 

Nejčtenější články
Nejlépe hodnocené články

 

Přihlášení k mému účtu

Uživatelské jméno:

Heslo: