changeset 2:9578b979556a

Import main code.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 28 Sep 2012 02:29:24 +0300
parents f5711d6e0cb5
children 672bd528de33
files krapula.c
diffstat 1 files changed, 1005 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/krapula.c	Fri Sep 28 02:29:24 2012 +0300
@@ -0,0 +1,1005 @@
+#include "dmlib.h"
+#include "dmargs.h"
+#include "dmvecmat.h"
+#include "dmres.h"
+#include "dmimage.h"
+
+#include "jss.h"
+#include "jssmod.h"
+#include "jssmix.h"
+#include "jssplr.h"
+
+#include <math.h>
+
+#define DM_COLORS (256)
+
+static BOOL dmDoLoadImage(SDL_Surface **img, const char *fname)
+{
+    DMResource *file = dmf_open(fname);
+    if (file == NULL)
+        return FALSE;
+
+    if ((*img = dmLoadImage(file)) == NULL)
+    {
+        dmf_close(file);
+        return FALSE;
+    }
+
+    dmf_close(file);
+    return TRUE;
+}
+
+#define LOADIMAGE(img, fname) \
+    do { \
+        if (!dmDoLoadImage(&(img), fname)) \
+            goto error_exit; \
+    } while (0)
+
+
+
+typedef struct
+{
+    int x, y;
+} DMCoords;
+
+
+typedef struct
+{
+    int x, y;
+    char *filename;
+    SDL_Surface *img;
+} DMCredits;
+
+
+typedef struct
+{
+    int currFrame, endTime, startTime, totalFrameTime;
+    BOOL pauseFlag, exitFlag;
+    SDL_Surface *screen;
+    SDL_Event event;
+} DMEngineData;
+
+
+typedef struct
+{
+    int currFrame, endTime, startTime;
+} DMFrameData;
+
+
+
+
+int optVFlags = SDL_SWSURFACE | SDL_HWPALETTE;
+int optScrWidth = 640, optScrHeight = 480;
+int optBenchmarkLen = 20;
+
+
+DMOptArg optList[] = {
+    { 0, '?', "help",       "Show this help", OPT_NONE },
+    { 2, 'v', "verbose",    "Be more verbose", OPT_NONE },
+    { 3, 'f', "fs",         "Fullscreen", OPT_NONE },
+//    { 5, 's', "size",       "Screen resolution -s 640x480", OPT_ARGREQ },
+};
+
+const int optListN = sizeof(optList) / sizeof(optList[0]);
+
+
+
+void argShowHelp()
+{
+    dmPrintBanner(stdout, dmProgName, "[options]");
+    dmArgsPrintHelp(stdout, optList, optListN);
+}
+
+
+BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
+{
+    switch (optN) {
+    case 0:
+        argShowHelp();
+        exit(0);
+        break;
+
+    case 2:
+        dmVerbosity++;
+        break;
+    
+    case 3:
+        optVFlags |= SDL_FULLSCREEN;
+        break;
+
+    case 5:
+        {
+            int w, h;
+            if (sscanf(optArg, "%dx%d", &w, &h) == 2)
+            {
+                if (w < 320 || h < 200 || w > 1024 || h > 768)
+                {
+                    dmError("Invalid width or height: %d x %d\n", w, h);
+                    return FALSE;
+                }
+                optScrWidth = w;
+                optScrHeight = h;
+            }
+            else 
+            {
+                dmError("Invalid size argument '%s'.\n", optArg);
+                return FALSE;
+            }
+        }
+        break;
+
+    default:
+        dmError("Unknown option '%s'.\n", currArg);
+        return FALSE;
+    }
+    
+    return TRUE;
+}
+
+
+void dmMakePalette(SDL_Surface *scr)
+{
+    SDL_Color pal[DM_COLORS];
+    int n;
+
+    for (n = 0; n < 256; n++)
+    {
+        pal[n].r = n;
+        pal[n].g = n;
+        pal[n].b = n;
+    }
+
+    SDL_SetColors(scr, pal, 0, DM_COLORS);
+}
+
+
+BOOL dmInitializeVideo(SDL_Surface **screen)
+{
+    *screen = SDL_SetVideoMode(optScrWidth, optScrHeight, 32, optVFlags); // | SDL_RESIZABLE);
+    if (*screen == NULL)
+    {
+        dmError("Can't SDL_SetVideoMode(): %s\n", SDL_GetError());
+        return FALSE;
+    }
+
+    SDL_ShowCursor(SDL_DISABLE);
+    return TRUE;
+}
+
+
+void dmRandom(SDL_Surface *screen, int q)
+{
+    Uint8 *pix = screen->pixels;
+    int xc, yc;
+    
+    for (yc = 0; yc < screen->h; yc++)
+    {
+        Uint8 *dp = pix;
+
+        for (xc = 0; xc < screen->w; xc++)
+            *dp++ = yc + (xc ^ q) + (yc & q);
+
+        pix += screen->pitch;
+    }
+}
+
+
+void dmPerlin(SDL_Surface *screen, float f)
+{
+    Uint8 *pix = screen->pixels;
+    int xc, yc;
+
+    for (yc = 0; yc < screen->h; yc++)
+    {
+        Uint8 *dp = pix;
+
+        for (xc = 0; xc < screen->w; xc++)
+        {
+            *dp++ = 128 + dmPerlinNoise2D(xc, yc, 0.01, 0.1, 3) / 34.0;
+        }
+
+        pix += screen->pitch;
+    }
+}
+
+
+#define QWIDTH	256
+#define QHEIGHT	160
+
+typedef Uint8 DMBlockMap[QHEIGHT][QWIDTH];
+
+
+static DMFloat dmClip(DMFloat a)
+{
+    return (a < 0.0f ? 0.0f : (a > 1.0f ? 1.0f : a));
+}
+
+
+void dmMakeBumpMap(DMBlockMap map, DMFloat q, DMFloat m)
+{
+    int x, y;
+    for (y = 0; y < QHEIGHT; y++)
+        for (x = 0; x < QWIDTH; x++)
+        {
+            DMFloat f = 0.40f + dmPerlinNoise2D(x, y, 1.1f, q, 2);
+            map[y][x] = (int) (dmClip(f) * m);
+        }
+}
+
+
+void dmShadowTraceHeightMap(DMBlockMap lightMap, DMBlockMap pheightMap, DMVector *light)
+{
+    int i, j;
+
+    for (j = 0; j < QHEIGHT; j++)
+        for (i = 0; i < QWIDTH; i++)
+        {
+            DMVector vr, vl, va;
+            DMFloat vrayLen, vfactor;
+            int vlen;
+            BOOL wasHit;
+
+            /* Perform shadow occlusion via simplistic raytracing */
+            vr.x = i;
+            vr.y = j;
+            vr.z = light->z; // - 10.0;
+//            vr.z = pheightMap[j][i];
+            
+            /* Calculate light vector vector */
+            dm_vector_sub_r(&vl, &vr, light);
+            vrayLen = dm_vector_length(&vl);
+            
+#if 1
+            dm_vector_copy(&va, &vl);
+            dm_vector_normalize(&va);
+            dm_vector_scale(&va, 0.6f);
+            dm_vector_copy(&vr, light);
+
+            vlen = 0;
+            wasHit = FALSE;
+            do
+            {
+                float h;
+                
+                /* If ray is inside the heightmap, get value */
+                if (vr.x >= 0 && vr.y >= 0 && vr.x < QWIDTH && vr.y < QHEIGHT)
+                    h = pheightMap[(int) vr.y][(int) vr.x];
+                else
+                    break;
+                
+                /* Check for hits */
+                if (h > vr.z)
+                    wasHit = TRUE;
+                else
+                {
+                    /* Move forwards */
+                    dm_vector_add(&vr, &va);
+                    vlen++;
+                }
+            }
+            while (!wasHit && vlen <= vrayLen);
+
+            /* Check if the ray hit something, e.g. is this point occluded? */
+            if (wasHit && vlen < vrayLen)
+            {
+                vfactor = vlen * 0.01;
+            }
+            else
+                vfactor = vlen * 0.02;
+#endif
+
+#if 1
+            {
+                /* Calculate light's intensity based on the angle it "hits"
+                 *
+                 * 1) Calculate the vectors that form the imaginary "plane"
+                 * 2) Cross-product -> normal vector of the plane
+                 * 2) Normalize the normal vector
+                 * 3) Calculate light vector's hit angle by dot product
+                 */
+                DMVector v1, v2;
+                DMFloat c;
+
+                v1.x = 2.0f;
+                v1.y = 0.0f;
+                v1.z = (DMFloat) (pheightMap[j][i] - pheightMap[j][i + 1]);
+
+                v2.x = 0.0f;
+                v2.y = 2.0f;
+                v2.z = (DMFloat) (pheightMap[j][i] - pheightMap[j + 1][i]);
+
+                dm_vector_cross(&vr, &v1, &v2);
+                dm_vector_normalize(&vr);
+                dm_vector_normalize(&vl);
+                c = dm_vector_dot(&vl, &vr);
+
+		vrayLen = 255 - (vrayLen * 0.1) * vrayLen + (c * 128.0f) + (vfactor * vfactor * 1255);
+            }
+#else
+            vrayLen = 255 - vrayLen * vrayLen * (vfactor * vfactor);
+            if (vrayLen < 0) vrayLen = 0;
+            vrayLen += pheightMap[j][i];
+#endif
+
+            /* Clip result */
+            if (vrayLen < 0)
+                vrayLen = 0;
+            else if (vrayLen > 255.0f)
+                vrayLen = 255.0f;
+
+            lightMap[j][i] = vrayLen;
+        }
+}
+
+
+void dmShadowTraceHeightMap2(DMBlockMap lightMap, DMBlockMap pheightMap, DMVector *light)
+{
+    int i, j;
+
+    light->z = 150;
+
+    for (j = 0; j < QHEIGHT; j++)
+        for (i = 0; i < QWIDTH; i++)
+        {
+            DMVector vr, vl, va;
+            DMFloat vrayLen, vfactor;
+            int vlen;
+            BOOL wasHit;
+
+            /* Perform shadow occlusion via simplistic raytracing */
+            vr.x = i;
+            vr.y = j;
+            vr.z = 200; //light->z; // - 10.0;
+            
+            /* Calculate light vector vector */
+            dm_vector_sub_r(&vl, &vr, light);
+            vrayLen = dm_vector_length(&vl);
+            
+#if 1
+            dm_vector_copy(&va, &vl);
+            dm_vector_normalize(&va);
+            dm_vector_copy(&vr, light);
+
+            vlen = 0;
+            wasHit = FALSE;
+            do
+            {
+                float h;
+                
+                /* If ray is inside the heightmap, get value */
+                if (vr.x >= 0 && vr.y >= 0 && vr.x < QWIDTH && vr.y < QHEIGHT)
+                    h = pheightMap[(int) vr.y][(int) vr.x];
+                else
+                    break;
+                
+                /* Check for hits */
+                if (h > vr.z)
+                    wasHit = TRUE;
+                else
+                {
+                    /* Move forwards */
+                    dm_vector_add(&vr, &va);
+                    vlen++;
+                }
+            }
+            while (!wasHit && vlen <= vrayLen);
+
+            /* Check if the ray hit something, e.g. is this point occluded? */
+            if (wasHit && vlen < vrayLen)
+            {
+                vfactor = vlen * 0.05;
+            }
+            else
+                vfactor = vlen * 0.001;
+#endif
+
+#if 0
+            {
+                /* Calculate light's intensity based on the angle it "hits"
+                 *
+                 * 1) Calculate the vectors that form the imaginary "plane"
+                 * 2) Cross-product -> normal vector of the plane
+                 * 2) Normalize the normal vector
+                 * 3) Calculate light vector's hit angle by dot product
+                 */
+                DMVector v1, v2;
+                DMFloat c;
+
+                v1.x = 2.0f;
+                v1.y = 0.0f;
+                v1.z = (DMFloat) (pheightMap[j][i] - pheightMap[j][i + 1]);
+
+                v2.x = 0.0f;
+                v2.y = 2.0f;
+                v2.z = (DMFloat) (pheightMap[j][i] - pheightMap[j + 1][i]);
+
+                dm_vector_cross(&vr, &v1, &v2);
+                dm_vector_normalize(&vr);
+                dm_vector_normalize(&vl);
+                c = dm_vector_dot(&vl, &vr);
+
+		vrayLen = 255 - (vrayLen * 0.1) * vrayLen + (c * 128.0f) + (vfactor * vfactor * 1255);
+            }
+#else
+            vrayLen = 255 - vrayLen * vrayLen * (vfactor * vfactor);
+            if (vrayLen < 0) vrayLen = 0;
+            vrayLen -= pheightMap[j][i];
+#endif
+
+            /* Clip result */
+            if (vrayLen < 0)
+                vrayLen = 0;
+            else if (vrayLen > 255.0f)
+                vrayLen = 255.0f;
+
+            lightMap[j][i] = vrayLen;
+        }
+}
+
+
+void dmAudioCallback(void *userdata, Uint8 *stream, int len)
+{
+    JSSMixer *d = (JSSMixer *) userdata;
+
+    if (d != NULL)
+    {
+        jvmRenderAudio(d, stream, len / jvmGetSampleSize(d));
+    }
+}
+
+#define DEBUG 0
+
+#define CREDITS_SPEED 1000
+#define CREDITS_RAND  4
+
+#define NOSFE_MIN     1
+#define NOSFE_MAX     269
+
+static const DMCoords randomCoords[] =
+{
+    { -300, -430 },
+    { 700, -550 },
+    { -200, 600 },
+    { 700, 600 }
+};
+const int nrandomCoords = sizeof(randomCoords) / sizeof(randomCoords[0]);
+
+
+static DMCredits credits[] =
+{
+    {   91,  223, "g4014.png", NULL },
+    {  151,  250, "g4026.png", NULL },
+    {  217,  227, "g4020.png", NULL },
+    {  173,  268, "g4032.png", NULL },
+    {  115,  359, "g4038.png", NULL },
+
+    {  437,  130, "g4062.png", NULL },
+    {  457,  102, "g4068.png", NULL },
+    {  450,  210, "g4056.png", NULL },
+
+    {  420,  320, "g4044.png", NULL },
+    {  486,  381, "g4050.png", NULL },
+};
+
+const int ncredits = sizeof(credits) / sizeof(credits[0]);
+
+
+DMEngineData engine;
+DMFrameData frame;
+
+SDL_Surface *nosfe[NOSFE_MAX - NOSFE_MIN + 1];
+
+SDL_Surface *logobg, *logolayer1, *logolayer2,
+            *anciat, *gay, *ruutu, *greets;
+
+
+int engineGetTick()
+{
+    return (frame.startTime - engine.startTime) + DEBUG * 1000;
+}
+
+float engineGetTimeDT()
+{
+    return (float) engineGetTick() / 1000.0f;
+}
+
+
+int engineGetTimeDTi()
+{
+    return (float) engineGetTick() / 1000;
+}
+
+
+int engineGetTime(int t)
+{
+    return engineGetTick() - (1000 * t);;
+}
+
+
+int engineGetDT(int t)
+{
+    return engineGetTime(t) / 1000;
+}
+
+
+int dmScaledBlitSurface32to32TransparentX(SDL_Surface *src, const int x0, const int y0, const int dwidth, const int dheight, SDL_Surface *dst);
+
+
+
+int main(int argc, char *argv[])
+{
+    BOOL initSDL = FALSE;
+    JSSModule *mod = NULL;
+    JSSMixer *dev = NULL;
+    JSSPlayer *plr = NULL;
+    int err, i;
+    DMResource *tfile;
+    SDL_AudioSpec *a_desired = NULL, *a_obtained = NULL;
+
+    memset(&frame, 0, sizeof(frame));
+    memset(&engine, 0, sizeof(engine));
+
+    dmInitProg("krapula", "Lauantai Aamun Krapula", "0.2", "(c) 2012 Anciat Prodz & TNSP", "PENIS.");
+    if (!dmArgsProcess(argc, argv, optList, optListN,
+        argHandleOpt, NULL, FALSE))
+        exit(1);
+
+    dmPrint(0, "%s\n", dmProgDesc);
+    dmPrint(0, "%s\n", dmProgAuthor);
+    dmPrint(0, "TNSP PIERUPASKA engine 2012 'passeli professional' loading.\n");
+
+    // Open packfs
+    if ((err = dmres_init("orvellys.dat", NULL, DRF_USE_PACK)) != DMERR_OK)
+    {
+        dmError("Could not initialize PACKFS: %d, %s.\n", err, dmErrorStr(err));
+        goto error_exit;
+    }
+
+    dmMsg(0, "miniJSS audio kyrpe XD\n");
+    jssInit();
+
+
+    // Dataa
+    dmPrint(0, "cachetaan motion jpeg videota (blz wait) ..");
+    for (i = 0; i < NOSFE_MAX; i++)
+    {
+        char fname[64];
+        snprintf(fname, sizeof(fname), "%08d.jpg", NOSFE_MIN + i);
+        dmPrint(0, ".");
+        
+        if (!dmDoLoadImage(&nosfe[i], fname))
+        {
+            dmError("Error uncompressing motion jpeg video.\n");
+            goto error_exit;
+        }
+    }
+    dmPrint(0,"ok\n");
+
+    dmPrint(0, "ogei, loput resurssit ..");
+    LOADIMAGE(logobg, "logobg.png");
+    LOADIMAGE(logolayer1, "logolayer1.png");
+    LOADIMAGE(logolayer2, "logolayer2.png");
+    LOADIMAGE(anciat, "anciat.png");
+    LOADIMAGE(gay, "gay.png");
+    LOADIMAGE(ruutu, "ruutu.png");
+    LOADIMAGE(greets, "greetings.png");
+
+    for (i = 0; i < ncredits; i++)
+        LOADIMAGE(credits[i].img, credits[i].filename);
+    
+    dmPrint(0, "joo\n");
+    
+    if ((tfile = dmf_open("krapula.xm")) == NULL)
+    {
+        dmError("EI VOINU MUSSIIKKIA LADATA!!!\n");
+        goto error_exit;
+    }
+    
+    err = jssLoadXM(tfile, &mod);
+    dmf_close(tfile);
+    if (err != DMERR_OK)
+    {
+        dmError("Joasdfok XM motuuli ei?? %d\n", err);
+        goto error_exit;
+    }
+
+
+    // Initialize SDL components
+    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) != 0)
+    {
+        dmError("Could not initialize SDL: %s\n", SDL_GetError());
+        goto error_exit;
+    }
+    initSDL = TRUE;
+
+
+    // Audio kyrvet
+    a_desired = dmMalloc(sizeof(SDL_AudioSpec));
+    a_obtained = dmMalloc(sizeof(SDL_AudioSpec));
+    if (!a_desired || !a_obtained)
+    {
+        fprintf(stderr, "Could not allocate SDL shit\n");
+        return 3;
+    }
+
+    a_desired->freq     = 48000;
+    a_desired->format   = AUDIO_S16SYS;
+    a_desired->channels = 2;
+
+    dmMsg(0, "Initializing miniJSS mixer with: %d, %d, %d\n",
+        JSS_AUDIO_S16, a_desired->channels, a_desired->freq);
+
+    dev = jvmInit(JSS_AUDIO_S16, a_desired->channels, a_desired->freq, JMIX_AUTO);
+    if (!dev)
+    {
+        dmError("jvmInit() returned NULL, voi perkele.\n");
+        goto error_exit;
+    }
+
+    a_desired->samples  = 16*1024;
+    a_desired->callback = dmAudioCallback;
+    a_desired->userdata = (void *) dev;
+
+    /* Open the audio device */
+    dmMsg(0, "Trying to init SDL audio with: %d, %d, %d\n",
+        a_desired->format, a_desired->channels, a_desired->freq);
+    
+    if (SDL_OpenAudio(a_desired, a_obtained) < 0)
+    {
+        dmError("Couldn't open audio: %s\n", SDL_GetError());
+        goto error_exit;
+    }
+
+    dmMsg(0, "SDL gave us: %d, %d, %d\n",
+        a_obtained->format, a_obtained->channels, a_obtained->freq);
+
+    if ((a_obtained->format   != a_desired->format) ||
+        (a_obtained->channels != a_desired->channels) ||
+        (a_obtained->freq     != a_desired->freq))
+    {
+        dmError("Could not get wanted audio parameters from SDL!\n");
+        goto error_exit;
+    }
+
+    dmFree(a_desired);
+
+    plr = jmpInit(dev);
+    if (!plr)
+    {
+        dmError("jmpInit() returned NULL\n");
+        goto error_exit;
+    }
+
+    /* Set callback, init module */
+    jvmSetCallback(dev, jmpExec, plr);
+    jmpSetModule(plr, mod);
+    jmpPlayOrder(plr, 0);
+    jvmSetGlobalVol(dev, 55);
+    
+    // Joo
+    if (!dmInitializeVideo(&engine.screen))
+        goto error_exit;
+
+    SDL_WM_SetCaption(dmProgDesc, dmProgName);
+
+    // efut
+    dmPerlinInit();
+
+    SDL_Surface *bmap = SDL_CreateRGBSurface(SDL_SWSURFACE, QWIDTH, QHEIGHT, 8, 0, 0, 0, 0);
+
+
+    dmMsg(0, "We are go.\n");
+    SDL_PauseAudio(0);
+
+    engine.startTime = SDL_GetTicks();
+
+
+    while (!engine.exitFlag)
+    {
+        
+        while (SDL_PollEvent(&engine.event))
+        switch (engine.event.type)
+        {
+            case SDL_KEYDOWN:
+                switch (engine.event.key.keysym.sym)
+                {
+                    case SDLK_ESCAPE:
+                        engine.exitFlag = TRUE;
+                        break;
+                    
+                    case SDLK_SPACE:
+                        engine.pauseFlag = !engine.pauseFlag;
+                        break;
+
+                    default:
+                        break;
+                }
+
+                break;
+
+#if 0
+            case SDL_VIDEORESIZE:
+                optScrWidth = engine.event.resize.w;
+                optScrHeight = engine.event.resize.h;
+
+                if (!dmInitializeVideo(&engine.screen))
+                    goto error_exit;
+                break;
+#endif
+            
+            case SDL_VIDEOEXPOSE:
+                break;
+
+            case SDL_QUIT:
+                engine.exitFlag = TRUE;
+                break;
+        }
+
+        // Draw frame
+        frame.startTime = SDL_GetTicks();
+
+        if (SDL_MUSTLOCK(engine.screen) != 0 && SDL_LockSurface(engine.screen) != 0)
+        {
+            dmError("Can't lock surface.\n");
+            goto error_exit;
+        }
+
+        float t = engineGetTimeDT();
+
+        if (t < 5)
+        {
+            int dt = engineGetTime(0);
+            static DMLerpContext lerpX, lerpY, lerpD;
+            static DMScaledBlitFunc nblit;
+            DMVector light;
+            static BOOL nollattu = FALSE;
+            if (!nollattu)
+            {
+                nblit = dmGetScaledBlitFunc(bmap->format, engine.screen->format, DMD_NONE);
+                dmMakePalette(bmap);
+                dmLerpInit(&lerpX, 0, QWIDTH, 5000);
+                dmLerpInit(&lerpY, QHEIGHT * 0.25, QHEIGHT * 0.75, 5000);
+                dmLerpInit(&lerpD, 0.04, 0.08, 5000);
+                nollattu = TRUE;
+            }
+
+            light.x = dmLerpSCurve(&lerpX, dt);
+            light.y = dmLerp1(&lerpY, dt);
+            light.z = 128;
+
+            dmShadowTraceHeightMap2(bmap->pixels, anciat->pixels, &light);
+            nblit(bmap, 0, 0, engine.screen->w, engine.screen->h, engine.screen);
+        }
+        else
+        if (t < 10)
+        {
+            int dt = engineGetTime(5);
+            static DMScaledBlitFunc nblit, kblit;
+            static DMLerpContext lerpD;
+            static BOOL nollattu = FALSE;
+
+            if (!nollattu)
+            {
+                nblit = dmGetScaledBlitFunc(logobg->format, engine.screen->format, DMD_TRANSPARENT);
+                kblit = dmGetScaledBlitFunc(logobg->format, engine.screen->format, DMD_NONE);
+                dmLerpInit(&lerpD, 0.01, 500, 10000);
+                nollattu = TRUE;
+            }
+
+            float q = dmLerpSCurve(&lerpD, dt);
+            float t = sin((float) dt / 150.0f);
+            int x = t * 25.0f + q, y = t * 35.0f + q*2.0f,
+                w = t * 70.0f + q, h = t * 40.0f + q*2.0f;
+
+            float t2 = sin((float) dt / 150.0f + 0.2f);
+            int x2 = t2 * 25.0f + q, y2 = t * 35.0f + q*2.0f,
+                w2 = t2 * 70.0f + q, h2 = t * 40.0f + q*2.0f;
+
+            kblit(logobg, 0, 0, engine.screen->w, engine.screen->h, engine.screen);
+            nblit(logolayer1, -x, -y, engine.screen->w+w, engine.screen->h+h, engine.screen);
+            nblit(logolayer2, -x2, -y2, engine.screen->w+w2, engine.screen->h+h2, engine.screen);
+        }
+        else
+        if (t < 20)
+        {
+            int dt = engineGetTime(10);
+            static DMLerpContext lerpX, lerpY, lerpD;
+            static DMScaledBlitFunc nblit, kblit;
+            static BOOL nollattu = FALSE;
+            DMVector light;
+            DMBlockMap heightMap;
+
+            if (!nollattu)
+            {
+                nblit = dmGetScaledBlitFunc(bmap->format, engine.screen->format, DMD_NONE);
+                kblit = dmGetScaledBlitFunc(logobg->format, engine.screen->format, DMD_TRANSPARENT);
+                dmMakePalette(bmap);
+                dmLerpInit(&lerpX, QWIDTH, 0, 10000);
+                dmLerpInit(&lerpY, QHEIGHT * 0.25, QHEIGHT * 0.75, 10000);
+                dmLerpInit(&lerpD, 0.04, 0.08, 10000);
+                nollattu = TRUE;
+            }
+
+            light.x = dmLerpSCurve(&lerpX, dt);
+            light.y = QHEIGHT * 0.5 + sin(dmLerp1(&lerpY, dt)) * 0.5;
+            light.z = 128;
+
+            dmMakeBumpMap(heightMap, dmLerpSCurve(&lerpD, dt), 254);
+
+            dmShadowTraceHeightMap(bmap->pixels, heightMap, &light);
+
+            nblit(bmap, 0, 0, engine.screen->w, engine.screen->h, engine.screen);
+
+            if ((dt / 100) % 10 < 5)
+            {
+                kblit(gay, 0, 0, engine.screen->w, engine.screen->h, engine.screen);
+            }
+        }
+        else
+        if (t < 45)
+        {
+            static int currState, currCredit, creditStartTime;
+            static DMLerpContext lerpX, lerpY, lerpZ;
+            static DMScaledBlitFunc nblit, kblit;
+            static BOOL stateChange, nollattu = FALSE;
+            int currFrame = engineGetTime(20) * 15 / 1000;
+            if (!nollattu)
+            {
+                dmClearSurface(ruutu, dmMapRGBA(ruutu, 0,0,0,0));
+                nblit = dmGetScaledBlitFunc(nosfe[0]->format, engine.screen->format, DMD_NONE);
+                kblit = dmGetScaledBlitFunc(credits[0].img->format, engine.screen->format, DMD_TRANSPARENT);
+                currCredit = -1;
+                currState = -1;
+                stateChange = TRUE;
+                nollattu = TRUE;
+            }
+
+            float gt = 1.0f + sin(engineGetTime(0) / 250.0f);
+            int g1 = gt * 25.0f, g2 = gt * 50.0f;
+            
+            nblit(nosfe[currFrame % NOSFE_MAX], -g1, -g1, engine.screen->w+g2, engine.screen->h+g2, engine.screen);
+
+            if (t >= 30)
+            {
+                int qtime = engineGetTime(30);
+                int creditTime = (engineGetTime(0) - creditStartTime);
+                float zscale;
+                if ( ( (qtime / (CREDITS_SPEED + 500)) % 2) == 0 && currState == -1)
+                    stateChange = TRUE;
+
+                if (stateChange && currCredit < ncredits)
+                {
+//                    fprintf(stderr, "[%6d] stateChange: st=%d, credit=%d\n", creditTime, currState, currCredit);
+                    stateChange = FALSE;
+                    switch (currState)
+                    {
+                        case 0:
+                            {
+                            int qt = (qtime / 100) % nrandomCoords;
+                            creditStartTime = engineGetTime(0);
+                            creditTime = 0;
+                            dmLerpInit(&lerpX, randomCoords[qt].x, credits[currCredit].x - 50, CREDITS_SPEED);
+                            dmLerpInit(&lerpY, randomCoords[qt].y, credits[currCredit].y - 50, CREDITS_SPEED);
+                            dmLerpInit(&lerpZ, 5.0f, 0.0f, CREDITS_SPEED);
+                            currState = 1;
+                            }
+                            break;
+
+                        case 2:
+                            if (creditTime >= CREDITS_SPEED)
+                                creditTime = CREDITS_SPEED - 1;
+
+                            zscale = dmLerpSCurve(&lerpZ, creditTime);
+                            dmScaledBlitSurface32to32TransparentX(
+                                credits[currCredit].img,
+                                dmLerpSCurve(&lerpX, creditTime) - (zscale * credits[currCredit].img->w),
+                                dmLerpSCurve(&lerpY, creditTime) - (zscale * credits[currCredit].img->h),
+                                credits[currCredit].img->w * (1.0f + zscale),
+                                credits[currCredit].img->h * (1.0f + zscale),
+                                ruutu);
+
+                            currState = -1;
+                            break;
+                        
+                        default:
+                            currCredit++;
+                            currState = 0;
+                            stateChange = TRUE;
+                            break;
+                    }
+
+//                    fprintf(stderr, "[%6d] changed: st=%d, credit=%d, chg=%d\n", creditTime, currState, currCredit, stateChange);
+                }
+                
+
+                if (currCredit > 0)
+                {
+                    kblit(ruutu, 0, 0, engine.screen->w, engine.screen->h, engine.screen);
+                }
+
+                if (currState == 1)
+                {
+                    if (creditTime >= CREDITS_SPEED)
+                    {
+                        creditTime = CREDITS_SPEED;
+                        stateChange = TRUE;
+                        currState = 2;
+                    }
+
+                    zscale = dmLerpSCurve(&lerpZ, creditTime);
+                    kblit(credits[currCredit].img,
+                        dmLerpSCurve(&lerpX, creditTime) - (zscale * credits[currCredit].img->w),
+                        dmLerpSCurve(&lerpY, creditTime) - (zscale * credits[currCredit].img->h),
+                        credits[currCredit].img->w * (1.0f + zscale),
+                        credits[currCredit].img->h * (1.0f + zscale),
+                        engine.screen);
+                }
+            }
+        }
+        else
+        if (t < 60)
+        {
+            int dt = engineGetTime(45);
+            static DMScaledBlitFunc nblit, kblit;
+            static DMLerpContext lerpD;
+            static BOOL nollattu = FALSE;
+
+            if (!nollattu)
+            {
+                nblit = dmGetScaledBlitFunc(logobg->format, engine.screen->format, DMD_TRANSPARENT);
+                kblit = dmGetScaledBlitFunc(logobg->format, engine.screen->format, DMD_NONE);
+                dmLerpInit(&lerpD, 0.01, 500, 10000);
+                nollattu = TRUE;
+            }
+
+            float q = dmLerpSCurve(&lerpD, dt);
+            float t = sin((float) dt / 150.0f);
+            int x = t * 25.0f + q, y = t * 35.0f + q,
+                w = t * 70.0f + q*2.0f, h = t * 40.0f + q*2.0f;
+
+            kblit(logobg, 0, 0, engine.screen->w, engine.screen->h, engine.screen);
+            nblit(greets, -x, -y, engine.screen->w+w, engine.screen->h+h, engine.screen);
+        }
+        else
+            engine.exitFlag = TRUE;
+
+        // Flip screen
+        if (SDL_MUSTLOCK(engine.screen) != 0)
+            SDL_UnlockSurface(engine.screen);
+
+        SDL_Flip(engine.screen);
+        SDL_Delay(20);
+        frame.endTime = SDL_GetTicks();
+
+        engine.currFrame++;
+        engine.totalFrameTime += frame.endTime - frame.startTime;
+    }
+
+    // Print benchmark results
+    engine.endTime = SDL_GetTicks();
+    dmMsg(0, "%d frames in %d ms, fps = %1.3f\n",
+        engine.currFrame, engine.endTime - engine.startTime,
+        (float) (engine.currFrame * 1000.0f) / (float) engine.totalFrameTime);
+
+
+error_exit:
+    dmMsg(0, "Shutting down.\n");
+    SDL_ShowCursor(SDL_ENABLE);
+    SDL_PauseAudio(1);
+
+    if (engine.screen)
+        SDL_FreeSurface(engine.screen);
+
+    jmpClose(plr);
+    jvmClose(dev);
+    jssFreeModule(mod);
+
+    dmres_close();    
+
+    if (initSDL)
+        SDL_Quit();
+
+    dmPrint(0, "SDL on muuten melko paska kirjasto.\n");
+    
+    return 0;
+}