Adventi naptár

Virtuális kandalló

Manapság sok játékban realisztikus háromdimenziós pályán ugrándozhatunk a főhősünkkel – a mai gépek és videókártyák számítási teljesítménye mellett ez kivitelezhető. Egykor egy sima forgó kockának a kirajzolása teljesen leterhelte a számítógépeket. Minél lassabb egy számítógép, annál nagyobb kihívás látványos animációkat kirajzolni vele.

A Demoscene szubkultúra programozóinak, grafikusainak, zenészeinek célja, hogy minél látványosabb, érdekesebb ún. demó programokat csináljanak, amelyekkel a programozói és művészi tudásukat mutatják be. A 90-es évek elején, amikor pl. az nVIDIA még sehol nem volt, a demókban már akkor is rengeteg valós időben számolt háromdimenziós animációt lehetett látni. Még régebben különféle effekteket találtak ki, mindig olyanokat, amelyek látványosak voltak, de egyszerűen, kevés számítással elő lehetett állítani őket. Ilyen az ún. plazma effekt, amellyel felhők is rajzolhatóak, de sok demóban lehetett látni animált fraktálokat is.

Viszonylag kevés számítással könnyen lehet tüzet is kirajzolni. Ha minden képkockánál az egyes pixelek értékét úgy állítsuk be, hogy azok az alatta lévő pixelek átlagai legyenek (mínusz 1, hogy hűlést belevegyük), akkor – valaki erre rájött – pont úgy kinéző animációt kapunk, mintha az tűz lenne. Csak az egyes értékekhez olyan színeket kell társítani, amelyek a tűzre jellemzőek, fekete (sötét) – piros – sárga – fehér (világos).

Az alábbi program ilyet rajzol. Hogy tényleg tűz legyen belőle, alulra véletlenszámokat kell berakni. És kész. Virtuális kandalló, hideg téli estékre!

#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL2_gfxPrimitives.h>


/* konfig */
enum { fullscreen = 0 };        /* 0 vagy 1 */
enum { w = 320, h = 240 };      /* kép méretei */


typedef unsigned char Kandallo[h][w];

/* az időzítéshez */
Uint32 idozit(Uint32 ms, void *param) {
    SDL_Event ev;
    ev.type = SDL_USEREVENT;
    SDL_PushEvent(&ev);
    return ms;
}


/* kiszamolja a megadott szin indexek kozott az atmenetet. */
void szineket_kiszamol(SDL_Surface *screen, Uint32 *bajtok) {
    /* megcsinálja a színátmeneteket */
    SDL_Color pal[256];
    struct {
        int pos;
        SDL_Color col;
    } szinek[] = {
        {  0,       {   0,   0,   0 } },
        { 16,       {   0,   0,  24 } },
        { 32,       {   0,   0,   0 } },
        { 96,       { 255,   0,   0 } },
        { 192,      { 255, 255,   0 } },
        { 255,      { 255, 255, 255 } },
        { -1 }
    };
    for (int i = 0; szinek[i].pos != -1; ++i) {
        int p1 = szinek[i].pos, p2 = szinek[i+1].pos;
        SDL_Color c1 = szinek[i].col, c2 = szinek[i+1].col;
        for (int j = p1; j <= p2; j++) {
            pal[j].r = c1.r + (c2.r - c1.r) * (j - p1) / (p2 - p1);
            pal[j].g = c1.g + (c2.g - c1.g) * (j - p1) / (p2 - p1);
            pal[j].b = c1.b + (c2.b - c1.b) * (j - p1) / (p2 - p1);
        }
    }
    /* és egyből át is számolja az adott sdl kép pixelformátumára */
    for (int i = 0; i < 256; ++i) {
        bajtok[i] = (pal[i].r >> screen->format->Rloss << screen->format->Rshift)
                  | (pal[i].g >> screen->format->Gloss << screen->format->Gshift)
                  | (pal[i].b >> screen->format->Bloss << screen->format->Bshift);
    }
}


void tuz_animacio(SDL_Surface *screen, Kandallo tuz, Uint32 szinek[]) {
    /* random az aljára: az égő fadarabok helyett. */
    /* random hosszúságú, random színű csíkokat rajzol. */
    int x = 0;
    while (x < w) {
        int szeles = 3 + rand() % 5, szin = rand() % 256, x1;
        for (x1 = x; x1 < x + szeles && x1 < w; ++x1)
            tuz[h - 1][x1] = szin;
        x += szeles + 5 + rand() % 5;
    }

    /* tűz effekt: minden képpont az alatta lévő képpontok átlaga,
     * és csökken (feketéhez közeledik) minden lépésben. */
    for (int y = 0; y < h; ++y) {
        int yp = y + 2;
        if (yp >= h)
            yp = h - 1;
        int yp2 = y + 3;
        if (yp2 >= h)
            yp2 = h - 1;
        for (int x = 1; x < w - 1; ++x) {
            /* itt az átlagolás */
            int res = (tuz[yp2][x] + tuz[yp][x] + tuz[yp][x + 1] + tuz[yp][x - 1]) / 4;
            /* -1, de csak ha nem csordul túl */
            if (res > 0)
                res--;
            tuz[y][x] = res;
        }
    }

    /* kirajzolás, a régebbi adventiben magyarázott módon, közvetlenül
     * állítgatva a pixeleket tároló bájtokat. */
    /* 2x méretűre nagyítja vízszintesen és függőlegesen is a képet. */
    for (int y = 0; y < h; ++y) {
        Uint32 *sor = (Uint32 *) ((char *) screen->pixels + y * 2 * screen->pitch);
        Uint32 *sor1 = (Uint32 *) ((char *) screen->pixels + (y * 2 + 1) * screen->pitch);
        for (int x = 0; x < w; ++x) {
            Uint32 szin = szinek[tuz[y][x]];
            sor[x * 2] = szin;
            sor[x * 2 + 1] = szin;
            sor1[x * 2] = szin;
            sor1[x * 2 + 1] = szin;
        }
    }
}


int main(int argc, char *argv[]) {
    /* SDL inicializálása, ablak létrehozása */
    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);
    SDL_Window *window = SDL_CreateWindow("Virtualis kandallo", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, w * 2, h * 2, fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
    if (window == NULL) {
        SDL_Log("Nem hozhato letre az ablak: %s", SDL_GetError());
        exit(1);
    }
    SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE);
    if (renderer == NULL) {
        SDL_Log("Nem hozhato letre a megjelenito: %s", SDL_GetError());
        exit(1);
    }
    SDL_Surface *screen = SDL_GetWindowSurface(window);
    Uint32 szinek[256];             /* színek az sdl pixelformátumában */
    szineket_kiszamol(screen, szinek);

    SDL_AddTimer(20, idozit, NULL);     /* 50 fps */

    /* fekete képről indulás */
    Kandallo tuz;                   /* a tűz tere */
    memset(tuz, 0, sizeof(tuz));

    bool quit = false;
    while (!quit) {
        SDL_Event event;
        SDL_WaitEvent(&event);

        switch (event.type) {
            case SDL_QUIT:
            case SDL_KEYDOWN:
                quit = true;
                break;

            case SDL_USEREVENT:
                tuz_animacio(screen, tuz, szinek);
                SDL_RenderPresent(renderer);
                break;
        }
    }

    SDL_Quit();

    return 0;
}