Eli pekin phongipalloista hieman muunneltu ja optimoidumpi versio. Yritin optimoida tätä aika hyvin ja samalla pitää selkeyden kans.
Toimii siis muuten samalla kaavalla, mutta ohjelman alussa lasketaan phongikartta taulukkoon, josta sitten otetaan piirtäessä värit, eikä pääpiirtoloopissa lasketa summaa ja erotusta kummempia laskutoimituksia. Samalla periaatteella voisi helposti tehdä vaikka bumpmapperin, plasmaefektin toho phongauksen päälle tai vaikka mitä kivaa.
Tehdään siis neljä kertaa ruutua isompi taulukko (kaksi kertaa leveämpi ja korkeampi), koska esim. jos pallo on vaikka aivan ruudun vasemmassa laidassa, oikea laita on ruudun verran kaukana pallosta, ja koska pallo on taulukon keskellä, niin taulukon pitää olla iso että siitä löytyisi pikselin väri.
Piirsin tästä kuvan, joka (toivottavasti) selventää jonku verran. Kuvassa on punaisella itse näyttöalue, ja vihreällä ja mustalla kaksi palloa, joista on piirretty myös buffereiden reunat, ja ruksit on siksi että keskikohta hahmottuisi paremmin. Kuvasta näkee, miten bufferit sijoittuvat näytön "päälle". pufin eka solu on siis bufferin vasen ylänurkka, WID ja HEI näyttöalueen koko (ja samalla bufferin keskikohta), ja PWID ja PHEI bufferin koko.
Tässä on kolme paikallaan pysyvää palloa, yksi vasemmassa reunassa "pomppiva", yksi ellipsiä pyörivä ja yksi hiirellä ohjattava. Ruudun koko ei ole mikään turhan älyttömän iso niinku pekin 640x480, tämä 400x300 riittää mainiosti. FPS on omalla koneellani (Athlon XP 2400+) aika mainio mun tekeleeksi, ja vempele pyörii kivan reaaliaikaisesti.
Filut on. Sorsat, selitekuva ja binäärit linuxille ja windowsille (vain viimeisimmästä, nopeimmasta versiosta)
HUOM
Nappasin selkeyden vuoksi hitaammat ja vanhemmat versiot vek.
Eka versio oli muute sama kuin tämä, paitsi puf-taulukossa oli x- ja y-ulottuvuudet "väärin päin", ensin x ja sitten y. Se on aika tyhmästi, koska pikselit ovat "väärin päin" muistissa (pikselit kulkevat silloin tyyliin ensin ylhäältä alas, sitten vasemmalta oikealle. en rupee tässä nyt selittämään). (kiitti viznutille huomautuksesta)
Tokassa versiossa oli sitte homma korjattu yksiulotteisella taulukolla, jossa pikselit oli "järjestyksessä". Siinä kuitenki laskin ite pari juttua, enkä ilmeisesti osannu optimoida sitä kunnolla.
Lopullinen, ja paras versio on tossa siis. Jos edelliset kiinnostaa niin niitä voi kysellä vaikka meilitse, vaan ei niissä mitään relevanttia o.
#include <SDL/SDL.h> #include <math.h> /* PI / 180, eli muuntokerroin asteista radiaaneiksi */ #define RADIKS 0.01745329251994329577 /* ikkunan kokomäärityksiä */ #define WID 400 #define HEI 300 /* P niinku pufferi */ #define PWID (2 * WID) #define PHEI (2 * HEI) #define KESKIX (WID / 2) #define KESKIY (HEI / 2) /* palleroiden määrä */ #define YHT 6 /* minkä tyyppiseksi määritellään esilaskettu taulukko (puf) * koklaa floatilla ja intillä, huomaa nopeusero ja miten * float näyttää paljon pehmeämmältä */ #define TYYPPI float typedef struct { int x, y; } pallero; void piirtele(SDL_Surface *screeni); void alustaPuf(); pallero pallerot[YHT] = {{0, HEI / 2}, {120, HEI / 2}, {220, HEI / 2}}; TYYPPI puf[PHEI][PWID]; int main(int argc, char **argv) { SDL_Surface *screeni; SDL_Color colorit[256]; SDL_Event eventti; int i, pomppu, rot, rotx[360], roty[360]; Uint32 aika, framet; if (SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "SDL ei initoitunut: %s\n", SDL_GetError()); return 0; } /* 8-bittiset värit ja paletti */ screeni = SDL_SetVideoMode(WID, HEI, 8, SDL_HWSURFACE | SDL_DOUBLEBUF | SDL_HWPALETTE); if (!screeni) { fprintf(stderr, "Videomoodin asetus epäonnas: %s\n", SDL_GetError()); SDL_Quit(); return 0; } SDL_WM_SetCaption("Phongipallerovempele", NULL); /* määritellään paletin värit ja asetetaan se */ for (i = 0; i < 256; i++) { colorit[i].r = i; colorit[i].g = i; colorit[i].b = i; } SDL_SetColors(screeni, colorit, 0, 256); /* esilasketaan soikiota pyörivän palleron sijainnit */ for (i = 0; i < 360; i++) { rotx[i] = KESKIX + 50 + 140 * sin(i * RADIKS); roty[i] = KESKIY + 80 * sin((i + 50) * RADIKS); } alustaPuf(); /* näistä lasketaan lopussa koko homman framemäärä ja aika */ framet = 0; aika = SDL_GetTicks(); /* pomppivaa palloa varten*/ pallerot[3].x = 30; pallerot[3].y = 60; pomppu = 0; while (1) { SDL_PollEvent(&eventti); if (eventti.type == SDL_QUIT || (eventti.type == SDL_KEYDOWN && eventti.key.keysym.sym == SDLK_ESCAPE) ) break; /* pikselien suoraa käsittelyä varten */ if (SDL_MUSTLOCK(screeni)) { if (SDL_LockSurface(screeni) < 0) { fprintf(stderr, "Screeni ei lukkiutunut: %s\n", SDL_GetError()); return 0; } } /* pomppiva pallo */ if (pallerot[3].y + pomppu > HEI) pomppu = -pomppu + 1; pallerot[3].y += pomppu; pomppu += 1; /* ellipsiä kiertävä pallo */ rot = framet % 360; pallerot[4].x = rotx[rot]; pallerot[4].y = roty[rot]; /* hiiren mukaan liikkuva pallero */ pallerot[5].x = eventti.motion.x; pallerot[5].y = eventti.motion.y; /* kai se o ikkunan sisällä? */ if (pallerot[5].x < 0) pallerot[5].x = 0; if (pallerot[5].x >= WID) pallerot[5].x = WID - 1; if (pallerot[5].y < 0) pallerot[5].y = 0; if (pallerot[5].y >= HEI) pallerot[5].y = HEI - 1; piirtele(screeni); if (SDL_MUSTLOCK(screeni)) SDL_UnlockSurface(screeni); SDL_Flip(screeni); framet++; } aika = SDL_GetTicks() - aika; SDL_Quit(); printf("Aika: %.3f sek, framet: %d kpl, fps: %.3f\n", aika / 1000., framet, 1000. * framet / aika); return 0; } /* päivittää näytön kuvan */ void piirtele(SDL_Surface *screeni) { int i, x, y; Uint8 *p; TYYPPI valo; /* osoitin näyttöpinnan pikseleihin */ p = (Uint8 *)screeni->pixels; /* tää oli aluksi helpommin tajuttavassa muodossa mutta optimoin pari * turhaa pluslaskua pois ni nopeutu fps:n tai kaks :o * tää voisi olla myös tyyliin: ylooppi alkaa nollasta ja loppuu HEI:hin * (näytön korkeus), eli piirretään siis jokainen näytöllä näkyvä pikseli. * puf on kaksi kertaa leveämpi kuin näyttö, ja siinä oleva "pallo" * on sen keskellä (eli koordinaateissa (WID, HEI)), jolloin nykyistä * palloa vastaava y-koordinaatti pufissa on HEI + y - palloy. * * selventävä esimerkki: jos pallo on vaikka 100 px päässä näytön * yläreunasta, niin kun y on 100 (eli ollaan kirkkaimassa kohdassa), * niin pufistakin pitäisi saada kirkkain kohta, eli keskikohta. * kun y = 100, niin saadaan HEI + y - palloy, jolloin y ja palloy ovat * samat, niin jää vain HEI, eli pufin keskikohta. * * Tai sitte siis ehkä pikkuse parempi selitys: * ensin siirrytään pufin keskelle (eli (WID, HEI)), ja sitten koska * x- ja y-koordinaatit ovat etäisyys _näytön vasemmasta yläkulmasta_, * siirrytään myös pufferissa oikealle alas eli _plussataan_ x ja y, * ja sitten lopuksi samaan tyyliin toisteppäi miinustetaan pallon * x- ja y-koordinaatit. * Katso myös selitteessä oleva linkki kuvaan. */ for (y = HEI; y < PHEI; y++) { for (x = WID; x < PWID; x++) { valo = 0; /* lasketaan vaa simppelisti yhteen oikeat kohdat esilasketusta * pufferista */ for (i = 0; i < YHT; i++) valo += puf[y - pallerot[i].y][x - pallerot[i].x]; if (valo > 255) valo = 255; /* pikselit ovat semmosessa järjestyksessä muistissa, että kun * y-looppi on ensin ja x sen sisällä, käydään pikselit itestään * läpi semmoisessa järjestyksessä, että voidaan vaan kasvattaa * osoitinta (siirtää se osoittaamaan seuraavaan tavuun muistissa) * samalla asetetaan sen nyt osoittaman muistin arvo valomääräksi */ *p++ = valo; } } } /* esilaskee phongpufferin */ void alustaPuf() { /* e niinku etäisyys */ double ex, ey, ez, ez2, e, valo, ee; int x, y; ez = 25; ez2 = ez * ez; for (y = 0; y < PHEI; y++) { for (x = 0; x < PWID; x++) { /* täytetään pufferin joka pikseli oikealla väriarvolla * kaavan toiminnan voi katsoa siitä pekin vinkistä */ ex = x - WID; ey = y - HEI; e = sqrt(ex * ex + ey * ey + ez2); ee = ez / e; valo = ee * 127 + ee * ee * 128; puf[y][x] = valo; } } }
Selkeät kommentit!
phongiympyrät :D
Hmm... Eikös ympyrä ole 2d, ja pallot 3d?
Eli nämä ovat ympyröitä eikä palloja ;)
Saisikos exeä näytille?
Jännää, nää näyttää ihan metaympyröiltä
Aika nopea, siistiä koodia soodakoodiks. ;)
Soodakoodi on paljon parempaa kuin pekikoodi ;D
ei toimi (Fatal signal: Segmentation Fault (SDL Parachute Deployed))
stderr.txt-tiedostossa
Hassu juttu hohoo, mulla linuxissa ja broidilla windowsissa on toiminut :o
Sama juttu, ei tykkäis toimia sit alkuunkaan :(
Segmentation Faultia pukkaa ainakin WinXP Pro:ssa. Veikkaisin, että jossain taulukossa on off-by-one virhe. Kokeile ajaa ohjelma valgrindin tai jonkin muun debuggaustyökalun kanssa.
Aika niinku "jännää". Ajoin ton valgrindin läpi. Koklasin samaa myös tekemälläni tyhjällä sdl-rungolla. Kivasti erroreita, virheiden määrä on sitä suurempi mitä kauemmin ohjelma on päällä.
tyhjasdl.c: http://paste.afraid.org?1ce37a3df6f1196263e6fc345b67f30c&v=cpp
valgrind tyhjasdl: http://paste.afraid.org?fc00eead7ef6d1c789a5144810ae4d23
valgrind phonki3: http://paste.afraid.org?a01a2b59c9fe49e48a15aba65d619902
noissa näyttäs olevan ihan samat virheet, eli mun ohjelmassa ei näyttäs olevan mitään :o
Jännä kun koklasin myös pelkkää SDL_Init() -- SDL_Quit() -paria, se tuotti myös hirveästi virheitä.
bugi
/* hiiren mukaan liikkuva pallero */ pallerot[5].x = eventti.motion.x; pallerot[5].y = eventti.motion.y;
debuggaus
pallerot[5].x = 34612, pallerot[5].y = 30675
Taitaa olla aika käyttöjärjestelmäkohtainen toi mitä toi eventti palauttaa, jos hiiri ei ole ikkunassa tai mitä se palauttaa ensimmäisellä kerralla.
Noniin, koodi korjattu, ja muutenkin siistitty, ja exen pitäisi toimia nyt...
Aika kiva.
Miksi tehdä neljä kertaa ruudun kokoinen bufferi, kun ihan vain ruudunkin kokoinen bufferi riittää? Samanlainenhan se on joka suuntaan keskipisteestä.
Tosiaan on, mutta ajattelin tämän tavan olevan nopeampi, kun voidaan käyttää pufferia suoraan, eikä tarvitse tarkistaa, mistä kohtaa pufferia piste sitten katottaisiin.
Enemmän koodia siihen vain tarvitsisi: neljä looppia yhden sijasta. Vauhtieroa tuskin mainittavasti tulee; muistinkäyttö vain putoaa neljännekseen bufferien osalta.
Minähän kikkailin noissa VB6-leikittelyissäni yhteen looppiin sen, mutta se ei tietty ole kovin tehokasta laskentaa.
Erinomainen esimerkki. Tuo soikiota pyörivä pallo pyörii itse asiassa Lissajouskuviota pitkin. http://mathworld.wolfram.com/LissajousCurve.html
Hähää, luulin tota alustaPuf-funktiota semmoseks alustan poispaukauttamisfunkkariks.
Aihe on jo aika vanha, joten et voi enää vastata siihen.