Tämän koodivinkin avulla C:n ja OpenGL:n perusteet tunteva voi helposti lukea yksinkertaisen 3D-mallin ja näyttää sen näytöllä OpenGL:n avulla.
Käytettävän 3d-mallimuodon määritelmä
Kommenttirivi alkaa #-merkillä. Verteksi -rivi alkaa v:llä, ja vektorin sijainti on liukulukuina järjestyksessä x,y,z. Polygoni alkaa p-merkillä, ja ensimmäinen numero on verteksien määrä. Loput ovat verteksien numerot. Verteksien ja Polygonien numerot kasvavat itsestään.
Tämä vinkki on muunneltavissa lukemaan .obj -muodon malleja varsin helposti (en itse ole tehnyt). Lisäksi, tässä esitetyt koodit ovat vain esimerkkejä. Ne eivät ole kovin monipuolisia, ja on suositeltavaa tehdä oma versio.
model.h
//Estetään tupla-määrittely #ifndef MODEL_H #define MODEL_H //Verteksi struct ModelVertex { float x, y, z; }; //Polygoni struct ModelPolygon { int nVertex; ModelVertex* VertexArray; }; //Malli struct ModelModel { int nVertex, nPolygon; ModelVertex* VertexArray; ModelPolygon* PolygonArray; }; //funktioiden prototyypit ModelModel* LoadModel(const char*); void FreeModel(ModelModel*); //lopetetaan ehto-osa #endif
model.cpp
#include <stdio.h> #include <stdlib.h> #include <memory.h> #include "model.h" //Mallin vapautus. Ei ihmeellistä. void FreeModel(ModelModel* model) { int i; for (i = 0; i < malli->nPolygon; ++i) { free(malli->PolygonArray[i]->VertexArray); } free(model->VertexArray); free(model->PolygonArray); free(model); } ModelModel* LoadModel(const char* FileName) { //Tarvitaan myöhemmin. int i, a, b; //Avataan tiedosto. FILE* fpt = fopen(FileName, "r"); //Alustetaan muuttuja. ModelModel* ret = (ModelModel*) calloc(1, sizeof(ModelModel)); memset(ret, 0, sizeof(ModelModel)); if(fpt!=NULL) { //Luetaan kuinka monta kertaa mikäkin asia esiintyy. while(1) { i=getc(fpt); if(i=='#')//Kommentti, kelataan rivin alkuun. { while(getc(fpt)!='\n'){} } else if(i=='v')//Verteksi { ret->nVertex++; } else if(i=='p')//Polygoni { ret->nPolygon++; } else if(i==EOF)break;//Jos Tiedoston loppu, lopetetaan luku... } rewind(fpt);//...ja palataan takaisin alkuun. //Alustetaan Verteksi- ja Polygonitaulukot. ret->VertexArray = (ModelVertex*) malloc(sizeof(ModelVertex) * ret->nVertex); ret->PolygonArray = (ModelPolygon*) malloc(sizeof(ModelPolygon) * ret->nPolygon); int nVertex=0, nPolygon=0, nPolygonV=0; while(1) { i=getc(fpt); if(i=='#') { while(getc(fpt)!='\n'){} } else if(i=='v') { fscanf(fpt," %f %f %f",&ret->VertexArray[nVertex].x,&ret->VertexArray[nVertex].y,&ret->VertexArray[nVertex].z);//Luetaan Verteksipisteet. nVertex++; } else if(i=='p') { fscanf(fpt," %d",&nPolygonV);//Luetaan Verteksien määrä. //Alustetaan Polygonin oma Verteksitaulukko. ret->PolygonArray[nPolygon].VertexArray = (ModelVertex*) malloc(sizeof(ModelVertex) * nPolygonV); ret->PolygonArray[nPolygon].nVertex=nPolygonV; for(a=0; a<nPolygonV; ++a) { fscanf(fpt," %d",&b); ret->PolygonArray[nPolygon].VertexArray[a]=ret->VertexArray[b];//Kopioidaan mallin verteksi Polygonin omaksi verteksiksi } nPolygon++; } else if(i==EOF)break; } fclose(fpt);//Suljetaan lukija. return ret;//Palautetaan arvot. } }
malli.model
#Tämä on neliö. v -0.5 -0.5 0.0 v -0.5 0.5 0.0 v 0.5 0.5 0.0 v 0.5 -0.5 0.0 p 4 0 1 2 3
toinenmalli.model
#Tämä on kuutio. #Koska muunsin tämän käsin .objista, jossa verteksit #ja polygonit lasketaan ykkösestä, niin #"nollaverteksi" on tyhjä. v 0.0 0.0 0.0 v -1.00000000 -1.00000000 1.00000000 v -1.00000000 1.00000000 1.00000000 v 1.00000000 1.00000000 1.00000000 v 1.00000000 -1.00000000 1.00000000 v -1.00000000 -1.00000000 -1.00000000 v -1.00000000 1.00000000 -1.00000000 v 1.00000000 1.00000000 -1.00000000 v 1.00000000 -1.00000000 -1.00000000 p 4 1 4 3 2 p 4 1 5 8 4 p 4 2 6 5 1 p 4 3 7 6 2 p 4 4 8 7 3 p 4 6 7 8 5
renderöinti
//Malli luetaan näin: ModelModel* malli=LoadModel("malli.model"); //Renderöinti OpenGL:llä: int a,b; for(a=0; a<malli->nPolygon; ++a) { glBegin(GL_POLYGON); for(b=0; b<malli->PolygonArray[a].nVertex; ++b) { glVertex3f(malli->PolygonArray[a].VertexArray[b].x,malli->PolygonArray[a].VertexArray[b].y,malli->PolygonArray[a].VertexArray[b].z); } glEnd(); } //Lopuksi vapautus: FreeModel(malli);
Kommentteja kiitos :)
Yritystä ja asennetta tuntuu ainakin olevan, se on hyvä. :)
Koodissa olisi vielä siistimisen varaa, välejä sopiviin kohtiin ja yhdenmukainen sisennys- ja rivityskäytäntö. Saisit kaikilta pitkiltä riveiltä pätkiä pilkkujen oikealta puolelta ja lasku- ja vertailuoperaattoreiden (+-*/=<> jne.) molemmilta puolilta väleillä, jotta koodi olisi selvempää ja jottei tämä sivu hajoaisi. Jos saan pyytää.
muuttuja = funktio(a->b[c].d[e].x + abc, a->b[c].d[e].y); /* Näin */ muuttuja=funktio(a->b[c].d[e].x+abc,a->b[c].d[e].y); /* Ei näin */
Kommenttikohdassa ehtosi on pielessä, tuossa rivin alkuun kelaamisessa pitäisi tietenkin olla erisuuruus. Ihan kokeilematta sanoisin, että tämä särkee koko systeemin, jos kommentti sisältää merkkejä p ja v. Tietysti kelauksessakin pitää muistaa varautua tiedoston loppuun (tai virhetilanteeseen), ettei viimeisellä rivillä oleva kommentti jätä ikuiseen silmukkaan.
Muistinkäsittelyn ja funktion kulun perusasioita: Funktio päättyy return-lauseeseen, joten sen jälkeen ei mitään voi vapautella. Lisäksi mallin vapauttaminen latausfunktion lopussa aiheuttaisi loogisesti sen, ettei sitä voi ohjelmassa käyttää, kun se on vapautettu jo.
Et vapauta missään noita polygonikohtaisia verteksitauluja, korjaapa tämä. Mallin yhteistä verteksitaulua et käytä kuin latausvaiheessa. Tarvittavat verteksipaikat (polygonien kulmat) voisi siis laskea muiden laskujen yhteydessä, jolloin muistin voisi varata yhtenä palana. Verteksitaulun voisi vaihtaa funktion sisäiseen väliaikaiseen tauluun. Jos tiedoston käy etukäteen läpi, voi varata koko mallin tarvitseman muistin yhdellä kertaa, jolloin vapautuksiakin tarvitaan vain yksi.
Voisit lisätä funktioon "return 0", jos muistin varaaminen epäonnistuu, ja kääntämällä järjestystä pääsisit yhdestä ylimääräisestä sisennyksestäkin eroon:
ret = calloc(...); if (ret == NULL) { fclose(tiedosto); /* Tiedosto pitää sulkea virhetapauksessakin! */ return NULL; /* Ja funktio päättyy tähän. */ } /* Lukeminen tänne, se tapahtuu vain, jos muistin varaus onnistui. */
Latausfunktion parametri kaipaisi const-määrettä, kun sitä ei kuitenkaan muuteta.
Annetaan lopuksi pari oleellista plussaa: Jako tiedostoihin on hoidettu asiallisesti ja rajapinta on yksinkertainen. Koodi on muutenkin melko pelkistettyä, ei ole turhia kiemuroita vaan vain oleelliset osat. Kerrankin joku on kyllin fiksu käyttääkseen omaa tietotyyppiä koordinaateille sen sijaan, että käyttäisi epäselvästi ja rumasti kolmen alkion taulukkoa. Tämä on yllättävän harvinaista! Erittäin hyvä vinkki, kun vielä korjaat muistin vapautuksen ja kommenttien käsittelyn.
Kiitos kehuista :)
Metabolix kirjoitti:
Koodissa olisi vielä siistimisen varaa, välejä sopiviin kohtiin ja yhdenmukainen sisennys- ja rivityskäytäntö.
Kun tein tämän omaan käyttöön, kaikki on sotkuista, olisitpa nähnyt alkuperäisen koodin! Sisennys on mitä on, kiitos Dev-c++:n niiiiiin ihanan IDEn. :)
Metabolix kirjoitti:
Saisit kaikilta pitkiltä riveiltä pätkiä pilkkujen oikealta puolelta ja lasku- ja vertailuoperaattoreiden (+-*/=<> jne.) molemmilta puolilta väleillä, jotta koodi olisi selvempää ja jottei tämä sivu hajoaisi.
Pitäisi kyllä.
Metabolix kirjoitti:
Kommenttikohdassa ehtosi on pielessä, tuossa rivin alkuun kelaamisessa pitäisi tietenkin olla erisuuruus. Ihan kokeilematta sanoisin, että tämä särkee koko systeemin, jos kommentti sisältää merkkejä p ja v.
Oho, en huomannutkaan, sillä en käytä malleissani (jotka muunnan puutteellisen tuen omalla .obj->.model muuntajallani) pahemmin kommentteja.
Metabolix kirjoitti:
Tietysti kelauksessakin pitää muistaa varautua tiedoston loppuun (tai virhetilanteeseen), ettei viimeisellä rivillä oleva kommentti jätä ikuiseen silmukkaan.
Muistinkäsittelyn ja funktion kulun perusasioita: Funktio päättyy return-lauseeseen, joten sen jälkeen ei mitään voi vapautella. Lisäksi mallin vapauttaminen latausfunktion lopussa aiheuttaisi loogisesti sen, ettei sitä voi ohjelmassa käyttää, kun se on vapautettu jo.
Hmm, mites sitten vapautan...?
Metabolix kirjoitti:
Et vapauta missään noita polygonikohtaisia verteksitauluja, korjaapa tämä. Mallin yhteistä verteksitaulua et käytä kuin latausvaiheessa. Tarvittavat verteksipaikat (polygonien kulmat) voisi siis laskea muiden laskujen yhteydessä, jolloin muistin voisi varata yhtenä palana. Verteksitaulun voisi vaihtaa funktion sisäiseen väliaikaiseen tauluun. Jos tiedoston käy etukäteen läpi, voi varata koko mallin tarvitseman muistin yhdellä kertaa, jolloin vapautuksiakin tarvitaan vain yksi.
Se yhteinen verteksitaulu oli sitä varten, kun alunperin oli tarkoitus tehdä että Polygonissa on int -muuttujat, joiden numero kertoo yhteisen taulun Verteksin numeron, joka sitten luetaan, eli Polygonilla itsellään EI ole Verteksipisteitä. :)
Metabolix kirjoitti:
Voisit lisätä funktioon "return 0", jos muistin varaaminen epäonnistuu, ja kääntämällä järjestystä pääsisit yhdestä ylimääräisestä sisennyksestäkin eroon:
ret = calloc(...); if (ret == NULL) { fclose(tiedosto); /* Tiedosto pitää sulkea virhetapauksessakin! */ return NULL; /* Ja funktio päättyy tähän. */ } /* Lukeminen tänne, se tapahtuu vain, jos muistin varaus onnistui. */
Metabolix kirjoitti:
Latausfunktion parametri kaipaisi const-määrettä, kun sitä ei kuitenkaan muuteta.
Onko välttämätöntä, vai vain selventävää...? O_o
Metabolix kirjoitti:
Annetaan lopuksi pari oleellista plussaa: Jako tiedostoihin on hoidettu asiallisesti ja rajapinta on yksinkertainen. Koodi on muutenkin melko pelkistettyä, ei ole turhia kiemuroita vaan vain oleelliset osat. Kerrankin joku on kyllin fiksu käyttääkseen omaa tietotyyppiä koordinaateille sen sijaan, että käyttäisi epäselvästi ja rumasti kolmen alkion taulukkoa. Tämä on yllättävän harvinaista! Erittäin hyvä vinkki, kun vielä korjaat muistin vapautuksen ja kommenttien käsittelyn.
Kiitos :)
"rumasti kolmen rivin alkiota" ...?
Haluaisin (vielä kerran) huomauttaa, että tein tämän omaan käyttöön, siksi mahdolliset omituiset ratkaisut. Tosin kyllä tämä on selvästi selkeämpi kuin normaalit purkkaviritelmäni. :)
Offtopic:
kray kirjoitti:
Kiitos :)
"rumasti kolmen rivin alkiota" ...?
Luepa uudestaan :)
TsaTsaTsaa kirjoitti:
Offtopic:
kray kirjoitti:
Kiitos :)
"rumasti kolmen rivin alkiota" ...?Luepa uudestaan :)
?
Niin että Metabolix ei puhunut kolmen rivin alkioista vaan kolmen alkion taulukoista.
Jaa :)
kray kirjoitti:
Metabolix kirjoitti:
Koodissa olisi vielä siistimisen varaa, välejä sopiviin kohtiin ja yhdenmukainen sisennys- ja rivityskäytäntö.
Kun tein tämän omaan käyttöön, kaikki on sotkuista, olisitpa nähnyt alkuperäisen koodin! Sisennys on mitä on, kiitos Dev-c++:n niiiiiin ihanan IDEn. :)
Ota editorin asetuksista ruksi pois kohdasta Smart Tabs ja lisää kohtaan Use Tab Character, elämäsi helpottuu. ;)
kray kirjoitti:
Metabolix kirjoitti:
Saisit kaikilta pitkiltä riveiltä pätkiä pilkkujen oikealta puolelta ja lasku- ja vertailuoperaattoreiden (+-*/=<> jne.) molemmilta puolilta väleillä, jotta koodi olisi selvempää ja jottei tämä sivu hajoaisi.
Pitäisi kyllä.
No muokkaisitko sitten vinkistäkin niin? :)
kray kirjoitti:
Hmm, mites sitten vapautan...?
Et luontifunktiossa mitenkään. Siksi sinulla on tuo vapautusfunktio erikseen, ja kaikki mallit pitää käytön jälkeen vapauttaa sillä.
Et vielä korjannut polygonikohtaisten verteksitaulujen vapautusta. Samalla tavalla ne pitäisi vapauttaa, vaikka tyyppi olisikin int.
for (i = 0; i < malli->nPolygon; ++i) { free(malli->PolygonArray[i]->VertexArray); }
kray kirjoitti:
Metabolix kirjoitti:
Latausfunktion parametri kaipaisi const-määrettä, kun sitä ei kuitenkaan muuteta.
Onko välttämätöntä, vai vain selventävää...? O_o
Erityisesti selventävää, mutta lisäksi se auttaa kääntäjää koodin optimoinnissa, kun voi luottaa, että tekstin sisältö säilyy samana. Ilman constia funktiota ei voi myöskään käyttää C++:n stringien kanssa edes s.c_str()-muunnoksella, koska sen palauttama arvo on tyypiltään juuri const char*
, jota ei tietenkään voi muuttaa vakiosta muuttujaksi, eikä muutenkaan vakiotekstillä:
LoadModel("asd"); // Vakioteksti, EI char* vaan const char*
Smart Tabs tomi :).
Suurin piirtein laitoin ne välit, Vapautuksen korjasin, laitoin const charin ja korjasin bugin renderöinnissä. Se nimittäin katsoi verteksien määrän aina ensimmäisestä polygonista.
Aihe on jo aika vanha, joten et voi enää vastata siihen.