From a20939b5f8c198bb532cb5407980fccfa0145c16 Mon Sep 17 00:00:00 2001 From: Ivan Mahonin Date: Jul 22 2020 07:31:25 +0000 Subject: opengl sprites --- diff --git a/src/SConstruct b/src/SConstruct index b70d8da..527da0e 100644 --- a/src/SConstruct +++ b/src/SConstruct @@ -18,7 +18,7 @@ opts.Save(name + '.conf', env) # config -libs = ['sdl2', 'SDL2_mixer', 'freetype2'] +libs = ['sdl2', 'SDL2_mixer', 'SDL2_image', 'freetype2'] ldflags = ' -lGL ' @@ -38,6 +38,7 @@ target = name headers = [ 'common.h', 'drawing.h', + 'font.h', 'group.h', 'sprite.h', 'world.h' ] @@ -51,11 +52,14 @@ sources = [ 'collider.c', 'common.c', 'drawing.c', + 'font.c', 'group.c', 'test.c', 'sound.c', 'sprite.c', - 'world.c' ] + 'world.c', + 'worldui.c', + 'blob.S' ] # build diff --git a/src/animation.c b/src/animation.c index 2e57434..d7e7a42 100644 --- a/src/animation.c +++ b/src/animation.c @@ -24,7 +24,7 @@ static float colorclamp(float x) static void mipx(float *buffer, int *w, int *h) { *w /= 2; if (*w <= 0) return; - for(float *d = buffer, *s = buffer, *end = d + (*w)*(*h); d < end; d += 4, s += 8) + for(float *d = buffer, *s = buffer, *end = d + (*w)*(*h)*4; d < end; d += 4, s += 8) for(int i = 0; i < 4; ++i) d[i] = (s[i] + s[i + 4])*0.5f; } @@ -33,9 +33,10 @@ static void mipx(float *buffer, int *w, int *h) { static void mipy(float *buffer, int *w, int *h) { *h /= 2; if (*h <= 0) return; - float *d = buffer, *s0 = buffer, *s1 = s0 + (*w)*4; - for(int r = 0; r < *h; ++r, s0 = s1, s1 += (*w)*4) - for(float *end = d + (*w); d < end; ++d, ++s0, ++s1) + int rstep = (*w)*4; + float *d = buffer, *s0 = buffer, *s1 = s0 + rstep; + for(float *end = d + (*h)*rstep; d < end; s0 += rstep, s1 += rstep) + for(float *rend = d + rstep; d < rend; ++d, ++s0, ++s1) *d = (*s0 + *s1)*0.5f; } @@ -70,21 +71,21 @@ static unsigned int loadFrame(const char *path) { SDL_LockSurface(surface); int w = surface->w; int h = surface->h; - size_t count = (size_t)w*h*4; + size_t rcount = (size_t)w*4; + size_t count = rcount*h; float *buffer = (float*)malloc(count*sizeof(float)); const unsigned char *pp = (const unsigned char *)surface->pixels; - for(float *p = buffer, *end = p + count; p < end; p += 4, pp += 4) { - if (pp[3]) { - float a = pp[3]/255.f; - p[0] = pp[0]/255.f*a; - p[1] = pp[1]/255.f*a; - p[2] = pp[2]/255.f*a; - p[3] = a; - } else { - p[0] = pp[0]/255.f; - p[1] = pp[1]/255.f; - p[2] = pp[2]/255.f; - p[3] = pp[3]/255.f; + for(float *p = buffer, *end = p + count; p < end; pp += surface->pitch - rcount) { + for(float *rend = p + rcount; p < rend; p += 4, pp += 4) { + if (pp[0]) { + float a = pp[0]/255.f; + p[0] = pp[3]/255.f*a; + p[1] = pp[2]/255.f*a; + p[2] = pp[1]/255.f*a; + p[3] = a; + } else { + p[0] = p[1] = p[2] = p[3] = 0; + } } } SDL_UnlockSurface(surface); @@ -146,9 +147,12 @@ static unsigned int loadFrame(const char *path) { glBindTexture(GL_TEXTURE_2D, texid); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + // build mip levels unsigned char *ibuffer = malloc((size_t)w*h*4); + int level = 0; while(1) { float *pp = buffer; for(unsigned char *p = ibuffer, *end = p + (size_t)w*h*4; p < end; p += 4, pp += 4) { @@ -165,7 +169,8 @@ static unsigned int loadFrame(const char *path) { p[3] = 0; } } - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, ibuffer); + glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, ibuffer); + ++level; if (w <= 1 && h <= 1) break; if (w > 1) mipx(buffer, &w, &h); diff --git a/src/blob.S b/src/blob.S index 639d80f..a23135e 100644 --- a/src/blob.S +++ b/src/blob.S @@ -2,7 +2,7 @@ .global heliBlobDefaultFontSize .section .rodata heliBlobDefaultFont: - .incbin "data/unifont.ttf" + .incbin "src/data/unifont.ttf" 1: heliBlobDefaultFontSize: .int 1b - heliBlobDefaultFont diff --git a/src/drawing.c b/src/drawing.c index fa2d6ce..f7fe979 100644 --- a/src/drawing.c +++ b/src/drawing.c @@ -3,8 +3,9 @@ #include "drawing.h" -double [4] = {0, 0, 0, 1}; +double heliDrawingColorStroke[4] = {0, 0, 0, 1}; +#define colorStroke heliDrawingColorStroke static double colorBack[4] = {1, 1, 1, 1}; static double colorFill[4] = {0.5, 0.5, 0.5, 1}; static double lineWidth = 1; @@ -20,7 +21,7 @@ void fill(const char *color) void noFill() { fill("transparent"); } void stroke(const char *color) - { heliParseColor(color, heliColorStroke); } + { heliParseColor(color, colorStroke); } void noStroke() { stroke("transparent"); } @@ -95,6 +96,7 @@ void resetPath() { pathSize = 0; } static void endPath(int close, int stroke, int fill) { + /* TODO:bw cairo_t *cr = heliCairo; if (cr && pathSize >= 8) { cairo_save(cr); @@ -114,6 +116,7 @@ static void endPath(int close, int stroke, int fill) { } cairo_restore(cr); } + */ resetPath(); } @@ -138,13 +141,18 @@ void moveTo(double x, double y) void heliDrawingClearFrame() { - glClearColor(0, 0, 0, 0); + glClearColor(colorBack[0], colorBack[1], colorBack[2], colorBack[3]); glClear(GL_COLOR_BUFFER_BIT); } void heliDrawingPrepareFrame() { resetPath(); heliDrawingClearFrame(); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + glDisable(GL_MULTISAMPLE); } void heliDrawingFinish() { @@ -152,10 +160,4 @@ void heliDrawingFinish() { free(path); path = NULL; pathAllocated = 0; - free(fontFamily); - fontFamily = NULL; - heliArrayDestroy(&fontCache); - if (ftInitialized && ftLibrary) FT_Done_FreeType(ftLibrary); - ftLibrary = NULL; - ftInitialized = FALSE; } diff --git a/src/font.c b/src/font.c index 28374a1..24d5806 100644 --- a/src/font.c +++ b/src/font.c @@ -4,6 +4,7 @@ #include "private.h" #include "font.h" +#include "drawing.h" @@ -147,8 +148,8 @@ static HeliGlyph* loadGlyph(HeliFontInstance *fi, int code) { offset.x = 64 - metrics->horiBearingX; offset.y = 64 + metrics->horiBearingY - metrics->height; FT_Set_Transform(fi->face, &matrix, &offset); - int success = !FT_Load_Glyph(fi->face, index, FT_LOAD_NO_HINTING)) - && !FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); + int success = !FT_Load_Glyph(fi->face, index, FT_LOAD_NO_HINTING) + && !FT_Render_Glyph(fi->face->glyph, FT_RENDER_MODE_NORMAL); matrix.xx = matrix.yy = 65536; FT_Set_Transform(fi->face, NULL, NULL); if (!success) return &blankGlyph; @@ -196,7 +197,6 @@ static HeliGlyph* loadGlyph(HeliFontInstance *fi, int code) { + (int)buffer[(r+1)*2*size + (c+1)*2] + 2) << 2); } } - free(buffer); glBindTexture(GL_TEXTURE_2D, 0); g->fi = fi; @@ -262,7 +262,7 @@ static void unloadFont(HeliFontInstance *fi) { static HeliGlyph* getGlyph(HeliFontInstance *fi, int code) { if (code < 0 || code >= FONT_MAX_GLYPHS) return &blankGlyph; - if (!fi->glyphs[code]) fi->glyphs[code] = loadGlyph(fi->font); + if (!fi->glyphs[code]) fi->glyphs[code] = loadGlyph(fi, code); return fi->glyphs[code]; } @@ -363,10 +363,10 @@ void text(const char *text, double x, double y) { } HeliGlyph *glyph = &blankGlyph; - if (!glyph->fi) glyph = loadGlyph(f, code); - if (!glyph->fi) glyph = loadGlyph(ff, code); - if (!glyph->fi) glyph = loadGlyph(f, FONT_BADCHAR_CODE); - if (!glyph->fi) glyph = loadGlyph(ff, FONT_BADCHAR_CODE); + if (!glyph->fi) glyph = getGlyph(f, code); + if (!glyph->fi) glyph = getGlyph(ff, code); + if (!glyph->fi) glyph = getGlyph(f, FONT_BADCHAR_CODE); + if (!glyph->fi) glyph = getGlyph(ff, FONT_BADCHAR_CODE); if (!glyph->fi) continue; HeliCharDesc *cc = &chars[line->end++]; @@ -454,7 +454,7 @@ void text(const char *text, double x, double y) { glPushMatrix(); glTranslated(x, y, 0); glScaled(fontScale, fontScale, 1); - glColor4dv(heliColorStroke); + glColor4dv(heliDrawingColorStroke); HeliFontMap *currentMap = NULL; glEnable(GL_TEXTURE_2D); glBegin(GL_QUADS); @@ -493,3 +493,11 @@ void text(const char *text, double x, double y) { glPopMatrix(); glColor4d(1, 1, 1, 1); } + + +void heliFontFinish() { + heliArrayDestroy(&fontCache); + if (ftInitialized && ftLibrary) FT_Done_FreeType(ftLibrary); + ftLibrary = NULL; + ftInitialized = FALSE; +} diff --git a/src/private.h b/src/private.h index 04453f7..a563fb7 100644 --- a/src/private.h +++ b/src/private.h @@ -133,12 +133,17 @@ void heliSpriteFinish(); // drawing -extern double heliColorStroke[4]; +extern double heliDrawingColorStroke[4]; void heliDrawingClearFrame(); void heliDrawingPrepareFrame(); void heliDrawingFinish(); +// font + +void heliFontFinish(); + + // sound void heliSoundUpdate(); diff --git a/src/sound.c b/src/sound.c index bc5b5a5..1672a79 100644 --- a/src/sound.c +++ b/src/sound.c @@ -1,6 +1,4 @@ -#include - #include #include @@ -19,7 +17,7 @@ typedef struct _HeliSoundInstance { typedef struct _HeliSoundTrashItem { Mix_Chunk *chunk; - gint64 time; + unsigned int time; } HeliSoundTrashItem; struct _Sound { @@ -37,10 +35,13 @@ static HeliSoundTrashItem *trash; static size_t trashSize; +static int less(unsigned int a, unsigned int b) + { return (b - a) < (1u << 31); } + static void trashAdd(Mix_Chunk *chunk) { if (!chunk) return; - gint64 time = g_get_monotonic_time() + 1000000; + unsigned int time = SDL_GetTicks() + 1000u; for(int i = 0; i < trashSize; ++i) if (!trash[i].chunk) { trash[i].chunk = chunk; trash[i].time = time; return; } @@ -55,9 +56,9 @@ static void trashAdd(Mix_Chunk *chunk) { } static void trashProcess() { - gint64 time = g_get_monotonic_time(); + unsigned int time = SDL_GetTicks(); for(int i = 0; i < trashSize; ++i) - if (trash[i].chunk && trash[i].time <= time) + if (trash[i].chunk && !less(time, trash[i].time)) { Mix_FreeChunk(trash[i].chunk); trash[i].chunk = NULL; } } diff --git a/src/sprite.c b/src/sprite.c index a8341a7..9a5238a 100644 --- a/src/sprite.c +++ b/src/sprite.c @@ -1,12 +1,11 @@ -#include - #include "private.h" #include "sprite.h" #include "world.h" #include "group.h" + struct _Sprite { double x; double y; @@ -360,7 +359,8 @@ Sprite worldGetSprite(int i) { return (Sprite)heliArrayGetValue(&sprites, i); } -static void drawSpriteDebug(cairo_t *cr, Sprite s) { +static void drawSpriteDebug(Sprite s) { + /* TODO:bw cairo_save(cr); cairo_set_line_width(cr, 0.5); cairo_translate(cr, s->x, s->y); @@ -395,67 +395,154 @@ static void drawSpriteDebug(cairo_t *cr, Sprite s) { cairo_show_text(cr, buf); cairo_restore(cr); + */ } -static void drawSprite(cairo_t *cr, Sprite s) { - cairo_save(cr); - cairo_translate(cr, s->x, s->y); - cairo_rotate(cr, s->rotation*(PI/180)); - cairo_scale(cr, s->scale*s->mirrorX, s->scale*s->mirrorY); +static void drawSprite(Sprite s) { + const double aaBorder = 10; - double hw = s->width*0.5; - double hh = s->height*0.5; + unsigned int texid = s->animation + ? (unsigned int)(size_t)heliArrayGetValue(&s->animation->frames, s->frame) : 0u; - cairo_surface_t *frame = s->animation - ? (cairo_surface_t*)heliArrayGetValue(&s->animation->frames, s->frame) : NULL; - - double a = s->tintColor[3]; - int tint = fabs(s->tintColor[0] - 1) > HELI_PRECISION - || fabs(s->tintColor[1] - 1) > HELI_PRECISION - || fabs(s->tintColor[2] - 1) > HELI_PRECISION; - int alpha = fabs(a) > HELI_PRECISION; - int visible = fabs(a) > HELI_PRECISION; + double color[4] = { + (texid ? 1.0 : s->shapeColor[0])*s->tintColor[0], + (texid ? 1.0 : s->shapeColor[1])*s->tintColor[1], + (texid ? 1.0 : s->shapeColor[2])*s->tintColor[2], + (texid ? 1.0 : s->shapeColor[3])*s->tintColor[3] }; - if (visible) { - if (tint) cairo_push_group(cr); + if (color[3] < HELI_PRECISION) return; + + double w = 0.5*s->scale*s->width; + double h = 0.5*s->scale*s->height; + if (w < HELI_PRECISION || h < HELI_PRECISION) return; + + glPushMatrix(); + glTranslated(s->x, s->y, 0); + glRotated(s->rotation, 0, 0, 1); + + if (aaBorder <= HELI_PRECISION) { + // use OpenGL multisample antialiasing + + glEnable(GL_MULTISAMPLE); - if (frame) { - // image - double ihw = cairo_image_surface_get_width(frame)*0.5; - double ihh = cairo_image_surface_get_height(frame)*0.5; - if (ihw > HELI_PRECISION && ihh > HELI_PRECISION) { - cairo_save(cr); - cairo_scale(cr, hw/ihw, hh/ihh); - cairo_set_source_surface(cr, frame, -ihw, -ihh); - if (alpha) cairo_paint_with_alpha(cr, a); else cairo_paint(cr); - cairo_restore(cr); + const int vcnt = 4; + const double vertices[][2] = { + { -w, -h }, + { w, -h }, + { -w, h }, + { w, h } }; + glColor4dv(color); + if (texid) { + const double texCoords[][2] = { + {0, 0}, + {1, 0}, + {0, 1}, + {1, 1} }; + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, texid); + + glBegin(GL_TRIANGLE_STRIP); + for(int i = 0; i < vcnt; ++i) { + glTexCoord2dv(texCoords[i]); + glVertex2dv(vertices[i]); } + glEnd(); + + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); } else { - // rectangle - cairo_set_source_rgba(cr, s->shapeColor[0], s->shapeColor[1], s->shapeColor[2], s->shapeColor[3]*a); - cairo_move_to(cr, -hw, -hh); - cairo_line_to(cr, hw, -hh); - cairo_line_to(cr, hw, hh); - cairo_line_to(cr, -hw, hh); - cairo_close_path(cr); - cairo_fill(cr); + glBegin(GL_TRIANGLE_STRIP); + for(int i = 0; i < vcnt; ++i) + glVertex2dv(vertices[i]); + glEnd(); } - if (tint) { - cairo_pattern_t *spriteGroup = cairo_pop_group(cr); - cairo_push_group(cr); - cairo_set_source(cr, spriteGroup); - cairo_paint(cr); - cairo_set_source_rgba(cr, s->tintColor[0], s->tintColor[1], s->tintColor[2], 1); - cairo_set_operator(cr, CAIRO_OPERATOR_MULTIPLY); - cairo_mask(cr, spriteGroup); - cairo_pop_group_to_source(cr); - cairo_paint(cr); - cairo_pattern_destroy(spriteGroup); + glDisable(GL_MULTISAMPLE); + } else { + // make antialiased borde manually + + double sideColor[4] = { color[0], color[1], color[2], 0 }; + + double w0 = w - 0.5*aaBorder; + double w1 = w + 0.5*aaBorder; + if (w0 < HELI_PRECISION) w0 = 0; + double h0 = h - 0.5*aaBorder; + double h1 = h + 0.5*aaBorder; + if (h0 < HELI_PRECISION) h0 = 0; + + const double k = (w1 < aaBorder ? w1/aaBorder : 1) + * (h1 < aaBorder ? h1/aaBorder : 1); + color[3] *= k; + + w0 *= s->mirrorX; + w1 *= s->mirrorX; + h0 *= s->mirrorY; + h1 *= s->mirrorY; + + const int vcnt = 14; + + const double vertices[][2] = { + { -w1, -h1 }, { -w0, -h0 }, + { w1, -h1 }, { w0, -h0 }, + { w1, h1 }, { w0, h0 }, + { -w1, h1 }, { -w0, h0 }, + { -w1, -h1 }, { -w0, -h0 }, + { -w0, -h0 }, + { w0, -h0 }, + { -w0, h0 }, + { w0, h0 } }; + + const double *colors[] = { + sideColor, color, + sideColor, color, + sideColor, color, + sideColor, color, + sideColor, color, + color, + color, + color, + color }; + + if (texid) { + const double tw0 = fabs(w0/w)*0.5 - 0.5; + const double tw1 = fabs(w1/w)*0.5 - 0.5; + const double th0 = fabs(h0/h)*0.5 - 0.5; + const double th1 = fabs(h1/h)*0.5 - 0.5; + const double texCoords[][2] = { + { -tw1, -th1 }, { -tw0, -th0 }, + { 1 + tw1, -th1 }, { 1 + tw0, -th0 }, + { 1 + tw1, 1 + th1 }, { 1 + tw0, 1 + th0 }, + { -tw1, 1 + th1 }, { -tw0, 1 + th0 }, + { -tw1, -th1 }, { -tw0, -th0 }, + { -tw0, -th0 }, + { 1 + tw0, -th0 }, + { -tw0, 1 + th0 }, + { 1 + tw0, 1 + th0 } }; + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, texid); + + glBegin(GL_TRIANGLE_STRIP); + for(int i = 0; i < vcnt; ++i) { + glColor4dv(colors[i]); + glTexCoord2dv(texCoords[i]); + glVertex2dv(vertices[i]); + } + glEnd(); + + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); + } else { + glBegin(GL_TRIANGLE_STRIP); + for(int i = 0; i < vcnt; ++i) { + glColor4dv(colors[i]); + glVertex2dv(vertices[i]); + } + glEnd(); } } - - cairo_restore(cr); + + glPopMatrix(); } void drawSprites() { @@ -489,17 +576,16 @@ void drawSprites() { } } - if (heliCairo) { - // draw - for(int i = 0; i < spritesSorted.count; ++i) { - Sprite s = (Sprite)(spritesSorted.items[i].value); - if (s->visible) drawSprite(heliCairo, s); - } - // draw debug marks - for(int i = 0; i < spritesSorted.count; ++i) { - Sprite s = (Sprite)(spritesSorted.items[i].value); - if (s->debug) drawSpriteDebug(heliCairo, s); - } + // draw + for(int i = 0; i < spritesSorted.count; ++i) { + Sprite s = (Sprite)(spritesSorted.items[i].value); + if (s->visible) drawSprite(s); + } + + // draw debug marks + for(int i = 0; i < spritesSorted.count; ++i) { + Sprite s = (Sprite)(spritesSorted.items[i].value); + if (s->debug) drawSpriteDebug(s); } } diff --git a/src/world.c b/src/world.c index 322be68..8b33c86 100644 --- a/src/world.c +++ b/src/world.c @@ -1,4 +1,6 @@ +#include + #include #include "private.h" @@ -6,30 +8,21 @@ #include "world.h" -static GtkWidget *window; - -static GtkWidget *dialogMessage; -static GtkWidget *dialogMessageLabel; -static GtkWidget *dialogAsk; -static GtkWidget *dialogAskLabel; -static GtkWidget *dialogAskEntry; -static GtkWidget *dialogAskMultiline; -static GtkWidget *dialogAskMultilineLabel; -static GtkWidget *dialogAskMultilineEntry; +static int sdlInitialized = 0; +SDL_Window *window; +SDL_GLContext context; static int started; static int stopped; -static int inDialog; static unsigned long long frameCount; -static gint64 startTime; -static gint64 currentTime; -static gint64 dialogTime; +static unsigned long long elapsedTimeUs; +static unsigned long long elapsedTimeSinceLastFrameUs; +static unsigned int prevFrameTimeMs; static Callback initCallback; static Callback drawCallback; static Callback deinitCallback; -static cairo_surface_t *cairoSurface; static int width = 400; static int height = 400; static int resizable; @@ -49,11 +42,6 @@ static int mouseMovedInFrame; static double _mouseX; static double _mouseY; -static char* buttonNames[] = { - "left", - "middle", - "right" }; -static int buttonsCount = (int)(sizeof(buttonNames)/sizeof(*buttonNames)); int keyEventGetCount(KeyEvent mode) @@ -106,11 +94,13 @@ int mousePressedOver(Sprite sprite) { return keyEventGetCount(KEYEVENT_MOUSE_DOWN) && mouseIsOver(sprite); } static void resize(int w, int h) { - width = w > 200 ? w : 100; - height = h > 200 ? h : 200; - if (started && !stopped && window) { - gtk_window_set_default_size(GTK_WINDOW(window), width, height); - gtk_widget_set_size_request(window, width, height); + w = w > 200 ? w : 100; + h = h > 200 ? h : 200; + if (width != w && height != h) { + width = w; + height = h; + if (started && !stopped && window) + SDL_SetWindowSize(window, width, height); } } @@ -130,7 +120,7 @@ void worldSetResizable(int r) { if (resizable == r) return; resizable = r ? TRUE : FALSE; if (started && !stopped && window) - gtk_window_set_resizable(GTK_WINDOW(window), resizable); + SDL_SetWindowResizable(window, resizable); } const char* worldGetTitle() @@ -143,7 +133,7 @@ void worldSetTitle(const char *t) { if (!t[i]) break; } if (changed && started && !stopped && window) - gtk_window_set_title(GTK_WINDOW(window), title); + SDL_SetWindowTitle(window, title); } double worldGetFrameRate() @@ -160,7 +150,7 @@ double worldGetTimeStep() int worldGetFrameCount() { return (int)frameCount; } double worldGetSeconds() - { return started ? (currentTime - startTime)*HELI_PRECISION : 0.0; } + { return started ? elapsedTimeUs*1e-6 : 0.0; } void worldSetInit(Callback init) { initCallback = init; } @@ -200,312 +190,216 @@ void cameraSetZoom(double x) { cameraZoom = x > HELI_PRECISION ? x : HELI_PRECISION; } -static void beginDialog() { - inDialog = TRUE; - dialogTime = g_get_monotonic_time() - startTime; -} - -static void endDialog() { - for(int i = 0; i < 100; ++i) gtk_main_iteration(); - currentTime = g_get_monotonic_time(); - startTime = currentTime - dialogTime; - inDialog = FALSE; -} - - -void messageBox(const char *message) { - if (!started || stopped || !window) - return; - beginDialog(); - gtk_label_set_label(GTK_LABEL(dialogMessageLabel), message); - gtk_dialog_run(GTK_DIALOG(dialogMessage)); - gtk_widget_hide(dialogMessage); - endDialog(); -} - -void askTextEx(const char *question, char *answer, int maxAnswerSize, int multiline, int password) { - if (!started || stopped || !window) - return; - beginDialog(); - if (multiline) { - gtk_label_set_label(GTK_LABEL(dialogAskMultilineLabel), question); - GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(dialogAskMultilineEntry)); - gtk_text_buffer_set_text(buffer, answer, -1); - gtk_dialog_run(GTK_DIALOG(dialogAskMultiline)); - gtk_widget_hide(dialogAskMultiline); - - GtkTextIter start, end; - gtk_text_buffer_get_bounds(buffer, &start, &end); - const char *a = gtk_text_buffer_get_text(buffer, &start, &end, FALSE); - for(int i = 0; i < maxAnswerSize; ++i) - { answer[i] = a[i]; if (!a[i]) break; } - } else { - gtk_label_set_label(GTK_LABEL(dialogAskLabel), question); - gtk_entry_set_text(GTK_ENTRY(dialogAskEntry), answer); - gtk_entry_set_input_purpose(GTK_ENTRY(dialogAskEntry), password ? GTK_INPUT_PURPOSE_PASSWORD : GTK_INPUT_PURPOSE_FREE_FORM); - gtk_entry_set_visibility(GTK_ENTRY(dialogAskEntry), !password); - gtk_dialog_run(GTK_DIALOG(dialogAsk)); - gtk_widget_hide(dialogAsk); - const char *a = gtk_entry_get_text(GTK_ENTRY(dialogAskEntry)); - for(int i = 0; i < maxAnswerSize; ++i) - { answer[i] = a[i]; if (!a[i]) break; } - } - endDialog(); -} - -void askText(const char *question, char *answer, int maxAnswerSize) - { askTextEx(question, answer, maxAnswerSize, FALSE, FALSE); } - - -static gboolean idle(gpointer data G_GNUC_UNUSED) { - if (stopped) { - if (window) { - gtk_window_close(GTK_WINDOW(window)); - window = NULL; - } - return FALSE; - } - gtk_widget_queue_draw(window); - return TRUE; -} - -static gboolean draw(GtkWidget *widget G_GNUC_UNUSED, cairo_t *cr, gpointer data G_GNUC_UNUSED) { - int doFrame = FALSE; +static void draw() { + unsigned int currentFrameTimeMs = SDL_GetTicks(); + unsigned long long deltaUs = frameCount == 0 ? 0 : (currentFrameTimeMs - prevFrameTimeMs)*1000ull; + prevFrameTimeMs = currentFrameTimeMs; - if ( cairoSurface - && cairo_image_surface_get_width(cairoSurface) != width - && cairo_image_surface_get_height(cairoSurface) != height ) - { cairo_surface_destroy(cairoSurface); cairoSurface = NULL; } - if (!cairoSurface) { - cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); - doFrame = TRUE; - } - - if (frameCount == 0) { - currentTime = startTime = g_get_monotonic_time(); - doFrame = TRUE; - } - double dt = 1/frameRate; - gint64 timeStep = (gint64)round(dt*1e6); - gint64 newTime = g_get_monotonic_time(); - if (currentTime < newTime) - doFrame = TRUE; - - if (inDialog) - doFrame = FALSE; - - if (doFrame) { - if (frameCount == 0) heliDoTests(); - - heliCairo = cairo_create(cairoSurface); - heliDrawingPrepareFrame(); - if (drawCallback) drawCallback(); - cairo_surface_flush(cairoSurface); - cairo_destroy(heliCairo); - heliCairo = NULL; - - currentTime += timeStep; + unsigned long long timeStepUs = (unsigned long long)round(dt*1e6); + elapsedTimeSinceLastFrameUs += deltaUs; + if (elapsedTimeSinceLastFrameUs > 2000000) + elapsedTimeSinceLastFrameUs = 2000000; + if (frameCount == 0 || elapsedTimeSinceLastFrameUs >= timeStepUs) { + elapsedTimeUs += timeStepUs; + if (frameCount != 0) elapsedTimeSinceLastFrameUs -= timeStepUs; ++frameCount; + heliSpriteUpdate(dt); heliSoundUpdate(); + glViewport(0, 0, width, height); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, width, height, 0, -1.0, 1.0); + glMatrixMode(GL_MODELVIEW); + + heliDrawingPrepareFrame(); + if (drawCallback) drawCallback(); + heliArrayClear(&keyEvents[KEYEVENT_KEY_WENTDOWN]); heliArrayClear(&keyEvents[KEYEVENT_KEY_WENTUP]); heliArrayClear(&keyEvents[KEYEVENT_MOUSE_WENTDOWN]); heliArrayClear(&keyEvents[KEYEVENT_MOUSE_WENTUP]); - if (stopped) g_idle_add_full(G_PRIORITY_DEFAULT, idle, NULL, NULL); + SDL_GL_SwapWindow(window); } - gint64 delta = newTime - currentTime - 2000000; - if (delta > 0) { - startTime += delta; - currentTime += delta; + unsigned long long addUs = elapsedTimeSinceLastFrameUs + (SDL_GetTicks() - prevFrameTimeMs)*1000ull; + if (addUs < timeStepUs) { + unsigned long long waitUs = timeStepUs - addUs; + if (waitUs > 2000) + SDL_Delay( (unsigned int)((waitUs + 500)/1000ull) ); } +} + + +static void deinit() { + if (context) SDL_GL_DeleteContext(context); + context = NULL; - heliDrawingClearFrame(cr); - cairo_save(cr); - cairo_set_source_surface(cr, cairoSurface, 0, 0); - cairo_paint(cr); - cairo_restore(cr); + if (window) SDL_DestroyWindow(window); + window = NULL; - if (!stopped) { - gtk_widget_queue_draw(window); - delta = currentTime - g_get_monotonic_time(); - if (delta > 100) g_usleep(delta - 100); + if (sdlInitialized) SDL_Quit(); + sdlInitialized = FALSE; +} + + +static int init() { + if (SDL_Init(SDL_INIT_EVERYTHING) < 0) { + printf("SDL_Init failed\n"); + deinit(); + return FALSE; + } + sdlInitialized = TRUE; + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 8); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetSwapInterval(1); + + unsigned int flags = SDL_WINDOW_OPENGL; + if (resizable) flags |= SDL_WINDOW_RESIZABLE; + window = SDL_CreateWindow( + title, + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + width, + height, + flags ); + + if (!window) { + printf("Cannot create window: %s\n", SDL_GetError()); + SDL_ClearError(); + deinit(); + return FALSE; + } + + context = SDL_GL_CreateContext(window); + if (!context) { + printf("Cannot create OpenGL context: %s\n", SDL_GetError()); + SDL_ClearError(); + deinit(); + return FALSE; } return TRUE; } -static gboolean handleEvent(GtkWidget *widget G_GNUC_UNUSED, GdkEvent *event, gpointer data G_GNUC_UNUSED) { - gchar *keyname; - int button; - - switch(event->type) { - case GDK_KEY_PRESS: - keyname = heliStringCopy( gdk_keyval_name(event->key.keyval) ); - heliLowercase(keyname); - keyEventAdd(KEYEVENT_KEY_DOWN, keyname); - keyEventAdd(KEYEVENT_KEY_WENTDOWN, keyname); - free(keyname); - return TRUE; - case GDK_KEY_RELEASE: - keyname = heliStringCopy( gdk_keyval_name(event->key.keyval) ); - heliLowercase(keyname); - keyEventRemove(KEYEVENT_KEY_DOWN, keyname); - keyEventAdd(KEYEVENT_KEY_WENTUP, keyname); - free(keyname); - return TRUE; - case GDK_BUTTON_PRESS: - button = event->button.button; - if (button >= 1 && button <= buttonsCount) { - keyEventAdd(KEYEVENT_MOUSE_DOWN, buttonNames[button - 1]); - keyEventAdd(KEYEVENT_MOUSE_WENTDOWN, buttonNames[button - 1]); - } - _mouseX = event->button.x; - _mouseY = event->button.y; - return TRUE; - case GDK_BUTTON_RELEASE: - button = event->button.button; - if (button >= 1 && button <= buttonsCount) { - keyEventRemove(KEYEVENT_MOUSE_DOWN, buttonNames[button - 1]); - keyEventAdd(KEYEVENT_MOUSE_WENTUP, buttonNames[button - 1]); - } - _mouseX = event->button.x; - _mouseY = event->button.y; - return TRUE; - case GDK_MOTION_NOTIFY: - _mouseX = event->motion.x; - _mouseY = event->motion.y; - return TRUE; - case GDK_FOCUS_CHANGE: - if (!event->focus_change.in) { + +static void handleEvent(SDL_Event *e) { + if (e->type == SDL_QUIT) { + stopped = TRUE; + } else + if (e->type == SDL_WINDOWEVENT) { + if (e->window.event == SDL_WINDOWEVENT_CLOSE) { + stopped = TRUE; + } else + if (e->window.event == SDL_WINDOWEVENT_RESIZED) { + width = e->window.data1; + height = e->window.data2; + } else + if (e->window.event == SDL_WINDOWEVENT_FOCUS_LOST) { int count = keyEventGetCount(KEYEVENT_KEY_DOWN); for(int i = 0; i < count; ++i) keyEventAdd(KEYEVENT_KEY_WENTUP, keyEventGet(KEYEVENT_KEY_DOWN, i)); heliArrayClear(&keyEvents[KEYEVENT_KEY_DOWN]); + + count = keyEventGetCount(KEYEVENT_MOUSE_DOWN); + for(int i = 0; i < count; ++i) + keyEventAdd(KEYEVENT_MOUSE_WENTUP, keyEventGet(KEYEVENT_MOUSE_DOWN, i)); + heliArrayClear(&keyEvents[KEYEVENT_MOUSE_DOWN]); } - break; - case GDK_DELETE: - stopped = TRUE; - window = NULL; - break; - default: - break; + } else + if (e->type == SDL_KEYDOWN || e->type == SDL_KEYUP) { + const char *keynameOrig = SDL_GetKeyName(e->key.keysym.sym); + if (keynameOrig && *keynameOrig) { + char *keyname = heliStringCopy(keynameOrig); + heliLowercase(keyname); + if (e->type == SDL_KEYDOWN) { + keyEventAdd(KEYEVENT_KEY_DOWN, keyname); + keyEventAdd(KEYEVENT_KEY_WENTDOWN, keyname); + } else { + keyEventRemove(KEYEVENT_KEY_DOWN, keyname); + keyEventAdd(KEYEVENT_KEY_WENTUP, keyname); + } + free(keyname); + } + } else + if (e->type == SDL_MOUSEBUTTONDOWN || e->type == SDL_MOUSEBUTTONUP) { + char *button = NULL; + switch(e->button.button) { + case SDL_BUTTON_LEFT: button = "left"; break; + case SDL_BUTTON_MIDDLE: button = "middle"; break; + case SDL_BUTTON_RIGHT: button = "right"; break; + default: break; + } + if (button) { + if (e->type == SDL_MOUSEBUTTONDOWN) { + keyEventAdd(KEYEVENT_MOUSE_DOWN, button); + keyEventAdd(KEYEVENT_MOUSE_WENTDOWN, button); + } else { + keyEventRemove(KEYEVENT_MOUSE_DOWN, button); + keyEventAdd(KEYEVENT_MOUSE_WENTUP, button); + } + } + _mouseX = e->button.x; + _mouseY = e->button.y; + } else + if (e->type == SDL_MOUSEMOTION) { + _mouseX = e->motion.x; + _mouseY = e->motion.y; } - return FALSE; + } -static void activate(GtkApplication* app, gpointer data G_GNUC_UNUSED) { - window = gtk_application_window_new(app); - g_signal_connect(window, "draw", G_CALLBACK(draw), NULL); - g_signal_connect(window, "event", G_CALLBACK(handleEvent), NULL); - gtk_window_set_resizable(GTK_WINDOW(window), resizable); - gtk_window_set_default_size(GTK_WINDOW(window), width, height); - gtk_window_set_title(GTK_WINDOW(window), title); - gtk_widget_add_events( window, - GDK_POINTER_MOTION_MASK - | GDK_BUTTON_PRESS_MASK - | GDK_BUTTON_RELEASE_MASK - | GDK_KEY_PRESS_MASK - | GDK_KEY_RELEASE_MASK - | GDK_FOCUS_CHANGE_MASK ); - gtk_widget_set_size_request(window, width, height); - gtk_widget_show(window); - - dialogMessage = gtk_dialog_new_with_buttons( - "", GTK_WINDOW(window), - GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, - "_OK", GTK_RESPONSE_OK, NULL); - gtk_dialog_set_default_response(GTK_DIALOG(dialogMessage), GTK_RESPONSE_OK); - dialogMessageLabel = gtk_label_new(""); - gtk_widget_show(dialogMessageLabel); - gtk_container_add( - GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialogMessage))), - dialogMessageLabel ); - - dialogAsk = gtk_dialog_new_with_buttons( - "", GTK_WINDOW(window), - GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, - "_OK", GTK_RESPONSE_OK, NULL); - gtk_dialog_set_default_response(GTK_DIALOG(dialogAsk), GTK_RESPONSE_OK); - GtkWidget *askBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); - dialogAskLabel = gtk_label_new(""); - dialogAskEntry = gtk_entry_new(); - gtk_entry_set_activates_default(GTK_ENTRY(dialogAskEntry), TRUE); - gtk_container_add(GTK_CONTAINER(askBox), dialogAskLabel); - gtk_container_add(GTK_CONTAINER(askBox), dialogAskEntry); - gtk_widget_show_all(askBox); - gtk_container_add( - GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialogAsk))), - askBox ); - - dialogAskMultiline = gtk_dialog_new_with_buttons( - "", GTK_WINDOW(window), - GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, - "_OK", GTK_RESPONSE_OK, NULL); - gtk_dialog_set_default_response(GTK_DIALOG(dialogAskMultiline), GTK_RESPONSE_OK); - GtkWidget *askMultilineBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); - dialogAskMultilineLabel = gtk_label_new(""); - dialogAskMultilineEntry = gtk_text_view_new(); - GtkWidget *askMultilineScrollBox = gtk_scrolled_window_new(NULL, NULL); - gtk_container_add(GTK_CONTAINER(askMultilineScrollBox), dialogAskMultilineEntry); - gtk_container_add(GTK_CONTAINER(askMultilineBox), dialogAskMultilineLabel); - gtk_container_add_with_properties( - GTK_CONTAINER(askMultilineBox), askMultilineScrollBox, - "expand", TRUE, "fill", TRUE, NULL ); - gtk_widget_show_all(askMultilineBox); - gtk_container_add_with_properties( - GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialogAskMultiline))), - askMultilineBox, - "expand", TRUE, "fill", TRUE, NULL ); - - currentTime = startTime = 0; - heliInitialized = TRUE; - if (initCallback) initCallback(); - g_idle_add(idle, NULL); + +static void run() { + while(!stopped) { + SDL_Event event; + while (SDL_PollEvent(&event)) + handleEvent(&event); + draw(); + } } + void worldRun() { if (started) return; started = TRUE; stopped = FALSE; - + frameCount = 0; + elapsedTimeUs = 0; + elapsedTimeSinceLastFrameUs = 0; srand(time(NULL)); - - GtkApplication *application = gtk_application_new(NULL, 0); - g_signal_connect(application, "activate", G_CALLBACK(activate), NULL); - g_application_run(G_APPLICATION(application), 0, NULL); - g_object_unref(application); - window = NULL; - dialogMessage = NULL; - dialogMessageLabel = NULL; - dialogAsk = NULL; - dialogAskEntry = NULL; - dialogAskLabel = NULL; - dialogAskMultiline = NULL; - dialogAskMultilineEntry = NULL; - dialogAskMultilineLabel = NULL; - if (deinitCallback) deinitCallback(); - - heliArrayClear(&heliObjectsSet); - heliSpriteFinish(); - heliDrawingFinish(); - heliAnimationFinish(); - heliSoundFinish(); - heliArrayDestroy(&heliObjectsSet); - - heliInitialized = FALSE; - - if (cairoSurface) cairo_surface_destroy(cairoSurface); - cairoSurface = NULL; - - for(int i = 0; i < keyEventsCount; ++i) - heliArrayDestroy(&keyEvents[i]); + if (init()) { + heliInitialized = TRUE; + + if (initCallback) initCallback(); + run(); + if (deinitCallback) deinitCallback(); + + heliArrayClear(&heliObjectsSet); + heliSpriteFinish(); + heliDrawingFinish(); + heliFontFinish(); + heliAnimationFinish(); + heliSoundFinish(); + heliArrayDestroy(&heliObjectsSet); + + heliInitialized = FALSE; + + deinit(); + + for(int i = 0; i < keyEventsCount; ++i) + heliArrayDestroy(&keyEvents[i]); + } started = FALSE; } diff --git a/src/worldui.c b/src/worldui.c new file mode 100644 index 0000000..63ea577 --- /dev/null +++ b/src/worldui.c @@ -0,0 +1,20 @@ + +#include + +#include "private.h" + +#include "world.h" + + +void messageBox(const char *message) { + // TODO:bw +} + + +void askTextEx(const char *question, char *answer, int maxAnswerSize, int multiline, int password) { + // TODO:bw +} + + +void askText(const char *question, char *answer, int maxAnswerSize) + { askTextEx(question, answer, maxAnswerSize, FALSE, FALSE); }