Kirjautuminen

Haku

Tehtävät

Keskustelu: Koodit: C: Phongipallojen paluu

Sivun loppuun

sooda [23.06.2005 16:53:28]

#

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;
        }
    }
}

BlueByte [24.06.2005 13:27:43]

#

Selkeät kommentit!

BlueByte [24.06.2005 13:57:45]

#

phongiympyrät :D

T.M. [25.06.2005 04:45:46]

#

Hmm... Eikös ympyrä ole 2d, ja pallot 3d?
Eli nämä ovat ympyröitä eikä palloja ;)

Saisikos exeä näytille?

tejeez [25.06.2005 10:14:55]

#

Jännää, nää näyttää ihan metaympyröiltä

peki [25.06.2005 16:01:44]

#

Aika nopea, siistiä koodia soodakoodiks. ;)

BlueByte [25.06.2005 17:52:28]

#

Soodakoodi on paljon parempaa kuin pekikoodi ;D

hohoo [25.06.2005 20:54:21]

#

ei toimi (Fatal signal: Segmentation Fault (SDL Parachute Deployed))
stderr.txt-tiedostossa

sooda [25.06.2005 20:57:07]

#

Hassu juttu hohoo, mulla linuxissa ja broidilla windowsissa on toiminut :o

tuomas [26.06.2005 00:27:49]

#

Sama juttu, ei tykkäis toimia sit alkuunkaan :(

FooBat [26.06.2005 02:01:56]

#

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.

sooda [26.06.2005 12:03:02]

#

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.af­raid.org?1­ce37a3df6f1196263e6fc345b67f30c&v=cpp
valgrind tyhjasdl: http://paste.af­raid.org?fc00ee­ad7ef6d1c789a5144810a­e4d23
valgrind phonki3: http://paste.af­raid.org?a01a2b59c9­fe49e48a15a­ba65d619902

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ä.

FooBat [26.06.2005 20:17:03]

#

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.

sooda [26.06.2005 23:33:49]

#

Noniin, koodi korjattu, ja muutenkin siistitty, ja exen pitäisi toimia nyt...

Meitsi [27.06.2005 21:22:55]

#

Aika kiva.

Merri [28.06.2005 15:51:42]

#

Miksi tehdä neljä kertaa ruudun kokoinen bufferi, kun ihan vain ruudunkin kokoinen bufferi riittää? Samanlainenhan se on joka suuntaan keskipisteestä.

sooda [28.06.2005 16:48:23]

#

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.

Merri [28.06.2005 19:03:34]

#

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.

olympos [21.11.2006 20:27:17]

#

Erinomainen esimerkki. Tuo soikiota pyörivä pallo pyörii itse asiassa Lissajouskuviota pitkin. http://mathworld.wolfram.com/LissajousCurve.html

moptim [23.09.2007 09:37:13]

#

Hähää, luulin tota alustaPuf-funktiota semmoseks alustan poispaukauttamisfunkkariks.


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta