diff --git a/SConstruct b/SConstruct index a457e7d..cf81332 100644 --- a/SConstruct +++ b/SConstruct @@ -1,3 +1,3 @@ build_dir = ARGUMENTS.get('build_dir', 'build') -VariantDir(build_dir, 'helianthus', duplicate = 0) +VariantDir(build_dir, 'src', duplicate = 0) SConscript(build_dir + '/SConstruct') diff --git a/data/sound/beep.ogg b/data/sound/beep.ogg deleted file mode 100644 index 71f5128..0000000 Binary files a/data/sound/beep.ogg and /dev/null differ diff --git a/data/sprite/breadball.png b/data/sprite/breadball.png deleted file mode 100644 index da1cba7..0000000 Binary files a/data/sprite/breadball.png and /dev/null differ diff --git a/data/sprite/bricks.png b/data/sprite/bricks.png deleted file mode 100644 index fe10e29..0000000 Binary files a/data/sprite/bricks.png and /dev/null differ diff --git a/demo/.gitignore b/demo/.gitignore new file mode 100644 index 0000000..b0bedda --- /dev/null +++ b/demo/.gitignore @@ -0,0 +1,2 @@ +/copy/ +/build/ diff --git a/demo/SConstruct b/demo/SConstruct new file mode 100644 index 0000000..cf81332 --- /dev/null +++ b/demo/SConstruct @@ -0,0 +1,3 @@ +build_dir = ARGUMENTS.get('build_dir', 'build') +VariantDir(build_dir, 'src', duplicate = 0) +SConscript(build_dir + '/SConstruct') diff --git a/demo/data/sound/beep.ogg b/demo/data/sound/beep.ogg new file mode 100644 index 0000000..71f5128 Binary files /dev/null and b/demo/data/sound/beep.ogg differ diff --git a/demo/data/sprite/breadball.png b/demo/data/sprite/breadball.png new file mode 100644 index 0000000..da1cba7 Binary files /dev/null and b/demo/data/sprite/breadball.png differ diff --git a/demo/data/sprite/bricks.png b/demo/data/sprite/bricks.png new file mode 100644 index 0000000..fe10e29 Binary files /dev/null and b/demo/data/sprite/bricks.png differ diff --git a/demo/src/SConstruct b/demo/src/SConstruct new file mode 100644 index 0000000..19b17e6 --- /dev/null +++ b/demo/src/SConstruct @@ -0,0 +1,26 @@ + + +import os +env = Environment() +env['ENV']['PKG_CONFIG_PATH'] = os.environ.get('PKG_CONFIG_PATH') + + +# config + +libs = ['helianthus'] +flags = ' -O0 -g -lm -Wall -fmessage-length=0 ' + + +# files + +sources = [ 'main.c' ] + + +# build + +env.ParseConfig('pkg-config --cflags --libs ' + ' '.join(libs)) + +env.Program( + target = 'demo', + source = sources, + parse_flags = flags ) diff --git a/demo/src/main.c b/demo/src/main.c new file mode 100644 index 0000000..06e17a7 --- /dev/null +++ b/demo/src/main.c @@ -0,0 +1,47 @@ + +#include + +Sprite ball, brick1, brick2; +Sound beep; + +void init() { + ball = createSpriteEx(200, 200, 64, 64); + spriteSetAnimation(ball, "data/sprite/breadball.png"); + + brick1 = createSpriteEx(200-32, 200+64, 64, 64); + spriteSetAnimation(brick1, "data/sprite/bricks.png"); + spriteSetTintColor(brick1, rgb(0, 0, 1)); + + brick2 = createSpriteEx(200+32, 200+64, 64, 64); + spriteSetAnimation(brick2, "data/sprite/bricks.png"); + spriteSetTintColor(brick2, rgba(1, 0, 1, 0.5)); + + beep = createSound("data/sound/beep.ogg"); +} + +void draw() { + double dt = worldGetTimeStep(); + double speed = 100; + + spritePointTo(ball, mouseX(), mouseY()); + + double x = spriteGetX(ball); + double y = spriteGetY(ball); + if (keyDown("left")) spriteSetX(ball, x - speed*dt); + if (keyDown("right")) spriteSetX(ball, x + speed*dt); + if (keyDown("up")) spriteSetY(ball, y - speed*dt); + if (keyDown("down")) spriteSetY(ball, y + speed*dt); + + spriteSetSpeedAndDirection(ball, speed/2, spriteGetRotation(ball)); + + if (mouseWentDown("left")) soundPlay(beep, FALSE); + + drawSprites(); +} + +int main() { + worldSetInit(&init); + worldSetDraw(&draw); + worldRun(); + return 0; +} diff --git a/dev-build.sh b/dev-build.sh new file mode 100755 index 0000000..d21ea79 --- /dev/null +++ b/dev-build.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +set -e + +scons PREFIX=$1 install +cd demo +scons diff --git a/helianthus/SConstruct b/helianthus/SConstruct deleted file mode 100644 index 4bcbc1d..0000000 --- a/helianthus/SConstruct +++ /dev/null @@ -1,40 +0,0 @@ - -env = Environment() - - -# config - -libs = ['gtk+-3.0', 'glib-2.0', 'cairo', 'SDL2_mixer'] - - -# compute build options - -flags = ' -O0 -g -lm -Wall -fmessage-length=0 ' - - -# files lists - -target = 'helianthus' - -sources = [ - 'animation.c', - 'array.c', - 'collider.c', - 'common.c', - 'drawing.c', - 'group.c', - 'test.c', - 'sound.c', - 'sprite.c', - 'world.c', - 'main.c' ] - - -# build - -env.ParseConfig('pkg-config --cflags --libs ' + ' '.join(libs)) - -env.Program( - target = target, - source = sources, - parse_flags = flags ) diff --git a/helianthus/animation.c b/helianthus/animation.c deleted file mode 100644 index 068d7c6..0000000 --- a/helianthus/animation.c +++ /dev/null @@ -1,76 +0,0 @@ - -#include -#include - -#include "private.h" - - -static HeliArray cache; - -static cairo_status_t read(void *closure, unsigned char *data, unsigned int length) { - return fread(data, length, 1, (FILE*)closure) - ? CAIRO_STATUS_SUCCESS : CAIRO_STATUS_READ_ERROR; -} - -static cairo_surface_t* loadFrame(const char *path) { - if (!heliStringEndsWithLowcase(path, ".png")) - return NULL; - FILE *f = fopen(path, "rb"); - if (f) { - cairo_surface_t *frame = cairo_image_surface_create_from_png_stream(&read, f); - fclose(f); - if (!frame) - fprintf(stderr, "helianthus: cannot load PNG content form file: %s\n", path); - return frame; - } - fprintf(stderr, "helianthus: cannot open image file: %s\n", path); - return NULL; -} - -static HeliAnimation* load(const char *path) { - HeliAnimation *a = calloc(1, sizeof(*a)); - a->path = heliStringCopy(path); - - GDir *dir = g_dir_open(path, 0, NULL); - if (dir) { - while(TRUE) { - const char* name = g_dir_read_name(dir); - if (!name) break; - char *p = heliStringConcat3(path, "/", name); - cairo_surface_t *frame = loadFrame(p); - free(p); - if (frame) heliArrayInsert(&a->frames, -1, frame, (HeliFreeCallback)&cairo_surface_destroy); - } - } else { - cairo_surface_t *frame = loadFrame(path); - if (frame) { - heliArrayInsert(&a->frames, -1, frame, (HeliFreeCallback)&cairo_surface_destroy); - } else { - fprintf(stderr, "helianthus: cannot load animation by path: %s\n", path); - } - } - - return a; -} - -static void unload(HeliAnimation *a) { - assert(!a->refcount); - free(a->path); - heliArrayDestroy(&a->frames); - free(a); -} - -HeliAnimation *heliCreateAnimation(const char *path) { - HeliPair *item = heliStringmapGet(&cache, path); - if (!item) item = heliStringmapAdd(&cache, path, load(path), (HeliFreeCallback)&unload); - HeliAnimation *a = (HeliAnimation*)item->value; - ++a->refcount; - return a; -} - -void heliAnimationUnref(HeliAnimation *a) - { if (--a->refcount <= 0) heliStringmapRemove(&cache, a->path); } - -void heliAnimationFinish() - { heliArrayDestroy(&cache); } - diff --git a/helianthus/array.c b/helianthus/array.c deleted file mode 100644 index d6b56d2..0000000 --- a/helianthus/array.c +++ /dev/null @@ -1,133 +0,0 @@ - -#include "private.h" - - -void heliPairInit(HeliPair *p) { - p->key = p->value = NULL; - p->freeKey = p->freeValue = NULL; -} - -void heliPairDestroy(HeliPair *p) { - if (p->key && p->freeKey) p->freeKey(p->key); - if (p->value && p->freeValue) p->freeKey(p->value); - heliPairInit(p); -} - -void heliArrayInit(HeliArray *a) { - a->items = NULL; - a->count = a->allocated = 0; -} - -void heliArrayDestroy(HeliArray *a) { - heliArrayClear(a); - free(a->items); - heliArrayInit(a); -} - -HeliPair* heliArrayGet(HeliArray *a, int i) - { return i >= 0 && i < a->count ? &a->items[i] : NULL; } - -void* heliArrayGetKey(HeliArray *a, int i) { - HeliPair *item = heliArrayGet(a, i); - return item ? item->key : NULL; -} - -void* heliArrayGetValue(HeliArray *a, int i) { - HeliPair *item = heliArrayGet(a, i); - return item ? item->value : NULL; -} - -HeliPair* heliArrayInsertPair(HeliArray *a, int i, void *k, void *v, HeliFreeCallback fk, HeliFreeCallback fv) { - if (i < 0 || i > a->count) - i = a->count; - if (a->allocated < a->count + 1) { - a->allocated += a->allocated/4 + 32; - a->items = realloc(a->items, a->allocated*sizeof(a->items[0])); - memset(&a->items[a->count], 0, (a->allocated - a->count)*sizeof(a->items[0])); - } - if (i < a->count) { - memmove(&a->items[i+1], &a->items[i], (a->count-i)*sizeof(a->items[0])); - memset(&a->items[i], 0, sizeof(a->items[0])); - } - ++a->count; - - HeliPair *item = &a->items[i]; - item->key = k; - item->value = v; - item->freeKey = fk; - item->freeValue = fv; - return item; -} - -HeliPair* heliArrayInsert(HeliArray *a, int i, void *v, HeliFreeCallback fv) - { return heliArrayInsertPair(a, i, NULL, v, NULL, fv); } - -void heliArrayRemove(HeliArray *a, int i) { - if (i < 0 || i >= a->count) return; - heliPairDestroy(&a->items[i]); - memmove(&a->items[i], &a->items[i+1], (a->count-i-1)*sizeof(a->items[0])); - --a->count; - memset(&a->items[a->count], 0, sizeof(a->items[0])); -} - -void heliArrayClear(HeliArray *a) { - while(a->count > 0) - heliArrayRemove(a, a->count-1); -} - -HeliPair* heliStringmapFind(HeliArray *a, const char *k, int *gtOrEqIndex) { - int i0 = 0; - int i1 = a->count - 1; - int cmp = i0 < a->count ? strcmp((char*)a->items[i0].key, k) : -1; - if (cmp >= 0) { - if (gtOrEqIndex) *gtOrEqIndex = i0; - return cmp ? NULL : &a->items[i0]; - } - if (i1 > i0) cmp = strcmp((char*)a->items[i1].key, k); - if (cmp < 0) { - if (gtOrEqIndex) *gtOrEqIndex = a->count; - return NULL; - } - if (cmp == 0) { - if (gtOrEqIndex) *gtOrEqIndex = i1; - return &a->items[i1]; - } - - while(i0 + 1 < i1) { - int i = (i0 + i1)/2; - cmp = strcmp((char*)a->items[i].key, k); - if (cmp == 0) { - if (gtOrEqIndex) *gtOrEqIndex = i; - return &a->items[i]; - } - if (cmp < 0) i0 = i; else i1 = i; - } - if (gtOrEqIndex) *gtOrEqIndex = i1; - return NULL; -} - -HeliPair* heliStringmapGet(HeliArray *a, const char *k) - { return heliStringmapFind(a, k, NULL); } - -HeliPair* heliStringmapAdd(HeliArray *a, const char *k, void *v, HeliFreeCallback fv) { - int i; - HeliPair *item = heliStringmapFind(a, k, &i); - if (item) { - if (item->value && item->freeValue) item->freeValue(item->value); - item->value = v; - item->freeValue = fv; - } else { - item = heliArrayInsertPair(a, i, heliStringCopy(k), v, &free, fv); - } - return item; -} - -int heliStringmapRemove(HeliArray *a, const char *k) { - int i; - if (heliStringmapFind(a, k, &i)) { - heliArrayRemove(a, i); - return TRUE; - } - return FALSE; -} - diff --git a/helianthus/collider.c b/helianthus/collider.c deleted file mode 100644 index 500cc28..0000000 --- a/helianthus/collider.c +++ /dev/null @@ -1,27 +0,0 @@ - -#include "private.h" - -int heliCheckCollision(HeliCollider *a, HeliCollider *b, double *normX, double *normY) { - // corner with side - // corner with circle - // circle with circle - #warning "TODO: heliCheckCollision: not implemented yet" - return FALSE; -} - -int heliPointCollision(HeliCollider *c, double x, double y) { - x -= c->x; - y -= c->y; - if (c->type == COLLIDER_CIRCLE) { - return x*x + y*y <= c->radius*c->radius; - } else - if (c->type == COLLIDER_RECTANGLE) { - double a = c->rotation*(PI/180); - double sn = sin(a); - double cn = cos(a); - return fabs(x*cn - y*sn) <= c->width*0.5 - && fabs(x*sn + y*cn) <= c->height*0.5; - } - return FALSE; -} - diff --git a/helianthus/common.c b/helianthus/common.c deleted file mode 100644 index a46a70f..0000000 --- a/helianthus/common.c +++ /dev/null @@ -1,96 +0,0 @@ - -#include "private.h" - - -cairo_t *heliCairo; - - -static char *colors[] = { - "transparent", "0 0 0 0", - - "black", "0 0 0 1", - "white", "1 1 1 1", - "gray", "0.5 0.5 0.5 1", - - "red", "1 0 0 1", - "green" "0 1 0 1", - "blue" "0 0 1 1", - - "yellow", "1 1 0 1", - "magenta", "1 0 1 1", - "cyan", "0 1 1 1", - - "yellow", "1 1 0 1", - "magenta", "1 0 1 1", - "cyan", "0 1 1 1", - - "brown", "0.5 0.5 0 1", -}; - - -int randomNumber(int min, int max) - { return max <= min ? min : rand()%(max - min + 1) + min; } - -double randomFloat() - { return (double)rand()/(double)RAND_MAX; } - - -char* heliStringCopy(const char *x) { - int len = strlen(x) + 1; - char *cp = malloc(len + 1); - memcpy(cp, x, len); - return cp; -} - -char* heliStringConcat3(const char *a, const char *b, const char *c) { - int la = strlen(a); - int lb = strlen(b); - int lc = strlen(c); - char *s = malloc(la + lb + lc + 1); - memcpy(s, a, la); - memcpy(s + la, b, lb); - memcpy(s + la + lb, c, lc); - s[la + lb + lc] = 0; - return s; -} - -int heliStringEndsWithLowcase(const char *s, const char *tail) { - int ls = strlen(s); - int lt = strlen(tail); - if (lt > ls) return FALSE; - for(int i = 0; i < lt; ++i) - if (tolower(s[ls - i]) != tolower(tail[lt - i])) - return FALSE; - return TRUE; -} - -void heliLowercase(char *x) - { while(*x) { *x = tolower(*x); ++x; } } - - -void heliParseColor(const char *x, double *color) { - color[0] = color[1] = color[2] = 0; - color[3] = 1; - - if (*x == '#') { - ++x; - int hex[8] = { 0, 0, 0, 0, 0, 0, 15, 15 }; - for(int i = 0; *x && i < 8; ++i) { - char c = tolower(*x); - if (c >= '0' && c <= '9') hex[i] = c - '0'; - if (c >= 'a' && c <= 'f') hex[i] = c - 'a' + 10; - } - for(int i = 0; i < 4; ++i) - color[i] = (hex[i*2]*16 + hex[i*2 + 1])/255.0; - } else - if (isalpha(*x)) { - int count = (int)(sizeof(colors)/sizeof(*colors)/2); - for(int i = 0; i < count; ++i) - if (strcmp(x, colors[i*2]) == 0) - { heliParseColor(colors[i*2 + 1], color); return; } - } else { - sscanf(x, "%lf %lf %lf %lf", &color[0], &color[1], &color[2], &color[3]); - } -} - - diff --git a/helianthus/common.h b/helianthus/common.h deleted file mode 100644 index 1a25cb9..0000000 --- a/helianthus/common.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef HELI_COMMON_H -#define HELI_COMMON_H - - -#ifndef TRUE -#define TRUE 1 -#endif - -#ifndef FALSE -#define FALSE 0 -#endif - -#ifndef PI -#define PI 3.14159265358979323846 -#endif - - -int randomNumber(int min, int max); -double randomFloat(); - - -#endif diff --git a/helianthus/drawing.c b/helianthus/drawing.c deleted file mode 100644 index ba5e348..0000000 --- a/helianthus/drawing.c +++ /dev/null @@ -1,197 +0,0 @@ - -#include - -#include "private.h" -#include "drawing.h" - -static double colorBack[4] = {1, 1, 1, 1}; -static double colorFill[4] = {0.5, 0.5, 0.5, 1}; -static double colorStroke[4] = {0, 0, 0, 1}; -static double lineWidth = 1; -static double *path; -static size_t pathSize; -static size_t pathAllocated; - -static HAlign horAlign = HALIGN_LEFT; -static VAlign vertAlign = VALIGN_TOP; -static char *font; -static double fontSize = 24; - - -void background(const char *color) - { heliParseColor(color, colorBack); } -void fill(const char *color) - { heliParseColor(color, colorFill); } -void noFill() - { fill("transparent"); } -void stroke(const char *color) - { heliParseColor(color, colorStroke); } -void noStroke() - { stroke("transparent"); } - -void strokeWeight(double weight) - { lineWidth = weight; } - -char* rgba(double r, double g, double b, double a) { - static char buf[1024]; - snprintf(buf, sizeof(buf) - 1, "%f %f %f %f", r, g, b, a); - return buf; -} - -char* rgb(double r, double g, double b) - { return rgba(r, g, b, 1); } - -void rect(double x, double y, double width, double height) { - resetPath(); - moveTo(x, y); - lineTo(x + width, y); - lineTo(x + width, y + height); - lineTo(x, y + height); - closePath(); -} - -void line(double x1, double y1, double x2, double y2) { - resetPath(); - moveTo(x1, y1); - lineTo(x2, y2); - strokePath(); -} - -void ellipse(double x, double y, double width, double height) { - resetPath(); - arcPath(x, y, width, height, 0, 360); - closePath(); -} - -void point(double x, double y) - { ellipse(x - lineWidth*0.25, y - lineWidth*0.25, lineWidth*0.5, lineWidth*0.5); } - -void arcPath(double x, double y, double w, double h, double start, double stop) { - double step = PI/180; - start *= PI/180; - stop *= PI/180; - if (start < stop) { double s = start; stop = start; start = s; } - for(double a = start; a < stop; a += step) { - double lx = x + cos(a)*w; - double ly = y + sin(a)*h; - lineTo(lx, ly); - } -} - -void arc(double x, double y, double w, double h, double start, double stop) { - resetPath(); - arcPath(x, y, w, h, start, stop); - strokePath(); -} - -void regularPolygon(double x, double y, int sides, double size) { - resetPath(); - size *= 0.5; - moveTo(x + size, y); - for(int i = 1; i < sides; ++i) { - double a = i*PI/sides; - lineTo(x + size*cos(a), y + size*sin(a)); - } - closePath(); -} - - -void resetPath() - { pathSize = 0; } - -static void endPath(int close, int stroke, int fill) { - cairo_t *cr = heliCairo; - if (cr && pathSize >= 8) { - cairo_save(cr); - cairo_move_to(cr, path[0], path[1]); - for(int i = 2; i < pathSize; i += 2) - cairo_line_to(cr, path[i], path[i+1]); - if (close) - cairo_close_path(cr); - if (fill) { - cairo_set_source_rgba(cr, colorFill[0], colorFill[1], colorFill[2], colorFill[3]); - if (stroke) cairo_fill_preserve(cr); else cairo_fill(cr); - } - if (stroke) { - cairo_set_line_width(cr, lineWidth); - cairo_set_source_rgba(cr, colorStroke[0], colorStroke[1], colorStroke[2], colorStroke[3]); - cairo_stroke(cr); - } - cairo_restore(cr); - } - resetPath(); -} - -static void pushPathPoint(double x, double y) { - if (pathAllocated < pathSize + 2) { - pathAllocated += pathAllocated/4 + 32; - path = realloc(path, pathAllocated); - memset(&path[pathSize], 0, (pathAllocated - pathSize)*sizeof(*path)); - } - path[pathSize++] = x; - path[pathSize++] = y; -} - -void closePath() - { endPath(TRUE, TRUE, TRUE); } -void strokePath() - { endPath(FALSE, TRUE, FALSE); } -void lineTo(double x, double y) - { pushPathPoint(x, y); } -void moveTo(double x, double y) - { resetPath(); lineTo(x, y); } - - -void textAlign(HAlign hor, VAlign vert) - { horAlign = hor; vertAlign = vert; } -void textFont(const char *f) - { free(font); font = heliStringCopy(f); } -void textSize(double size) - { fontSize = size; } - -void text(const char *text, double x, double y) { - resetPath(); - - cairo_t *cr = heliCairo; - if (!cr) return; - cairo_save(cr); - - if (font) cairo_select_font_face(cr, font, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); - cairo_set_font_size(cr, fontSize); - - cairo_text_extents_t extents; - cairo_text_extents(cr, text, &extents); - double w = extents.x_bearing + extents.width; - double h = extents.y_bearing + extents.height; - if (horAlign == HALIGN_CENTER) x -= w*0.5; - if (horAlign == HALIGN_RIGHT ) x -= w; - if (vertAlign == VALIGN_CENTER) y -= h*0.5; - if (vertAlign == VALIGN_BOTTOM) y -= h; - - cairo_set_source_rgba(cr, colorStroke[0], colorStroke[1], colorStroke[2], colorStroke[3]); - cairo_move_to(cr, x, y); - cairo_show_text(cr, text); - - cairo_restore(cr); -} - -void heliDrawingClearFrame(cairo_t *cr) { - cairo_save(cr); - cairo_set_source_rgba(cr, colorBack[0], colorBack[1], colorBack[2], 1); - cairo_paint(cr); - cairo_restore(cr); -} - -void heliDrawingPrepareFrame() { - resetPath(); - if (heliCairo) heliDrawingClearFrame(heliCairo); -} - -void heliDrawingFinish() { - resetPath(); - free(path); - path = NULL; - pathAllocated = 0; - free(font); - font = NULL; -} diff --git a/helianthus/drawing.h b/helianthus/drawing.h deleted file mode 100644 index 3fa6f4a..0000000 --- a/helianthus/drawing.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef HELI_DRAWING_H -#define HELI_DRAWING_H - - -#include "common.h" - -typedef enum _HAlign { - HALIGN_LEFT, - HALIGN_CENTER, - HALIGN_RIGHT -} HAlign; - -typedef enum _VAlign { - VALIGN_TOP, - VALIGN_CENTER, - VALIGN_BOTTOM -} VAlign; - - -void background(const char *color); -void fill(const char *color); -void noFill(); -void stroke(const char *color); -void noStroke(); -void strokeWeight(double weight); -char* rgb(double r, double g, double b); -char* rgba(double r, double g, double b, double a); -void rect(double x, double y, double width, double height); -void ellipse(double x, double y, double width, double height); -void text(const char *text, double x, double y); -void textAlign(HAlign hor, VAlign vert); -void textFont(const char *font); -void textSize(double size); -void arc(double x, double y, double w, double h, double start, double stop); -void arcPath(double x, double y, double w, double h, double start, double stop); -void line(double x1, double y1, double x2, double y2); -void point(double x, double y); -void regularPolygon(double x, double y, int sides, double size); - -void moveTo(double x, double y); -void lineTo(double x, double y); -void resetPath(); -void closePath(); -void strokePath(); - - -#endif - diff --git a/helianthus/group.c b/helianthus/group.c deleted file mode 100644 index 400bffd..0000000 --- a/helianthus/group.c +++ /dev/null @@ -1,209 +0,0 @@ - -#include "private.h" -#include "group.h" -#include "world.h" - -struct _Group { - HeliArray sprites; -}; - - -Group createGroup() { - Group g = calloc(1, sizeof(*g)); - return g; -} - -Group createEdgesGroupEx(double x1, double y1, double x2, double y2, double borderWidth) { - if (x2 < x1) { double x = x2; x2 = x1; x1 = x; } - if (y2 < y1) { double y = y2; y2 = y1; y1 = y; } - if (borderWidth < 0.1) borderWidth = 0.1; - - double b = borderWidth; - double w = x2 - x1 + b*2; - double h = y2 - y1 + b*2; - x1 -= b; - y1 -= b; - - Group g = createGroup(); - groupAdd(g, createSpriteEx(x1, y1, w, b)); - groupAdd(g, createSpriteEx(x1, y2, w, b)); - groupAdd(g, createSpriteEx(x1, y1, b, h)); - groupAdd(g, createSpriteEx(x2, y1, b, h)); - return g; -} - -Group createEdgesGroup() - { return createEdgesGroupEx(0, 0, worldGetWidth(), worldGetHeight(), 10); } - - -void groupDestroy(Group group) { - heliArrayDestroy(&group->sprites); - free(group); -} - -void groupDestroyEx(Group group, int destroySprites) { - groupClearEx(group, destroySprites); - groupDestroy(group); -} - -void groupAdd(Group group, Sprite sprite) { - if (groupContains(group, sprite)) return; - heliArrayInsert(&group->sprites, -1, sprite, NULL); - heliArrayInsert(heliSpriteGetGroups(sprite), -1, sprite, NULL); -} - -void groupRemove(Group group, Sprite sprite) { - for(int i = group->sprites.count-1; i >= 0; --i) - if (group->sprites.items[i].value == sprite) - heliArrayRemove(&group->sprites, i); - HeliArray *groups = heliSpriteGetGroups(sprite); - for(int i = groups->count-1; i >= 0; --i) - if (groups->items[i].value == group) - heliArrayRemove(groups, i); -} - -void groupClearEx(Group group, int destroySprites) { - while(groupGetCount(group) > 0) { - Sprite s = groupGet(group, 0); - groupRemove(group, s); - if (destroySprites) spriteDestroy(s); - } -} - -void groupClear(Group group) - { groupClearEx(group, FALSE); } -void groupDestroyEach(Group group) - { groupClearEx(group, TRUE); } - -int groupContains(Group group, Sprite sprite) { - for(int i = group->sprites.count-1; i >= 0; --i) - if (group->sprites.items[i].value == sprite) - return TRUE; - return FALSE; -} - -int groupGetCount(Group group) - { return group->sprites.count; } -Sprite groupGet(Group group, int i) - { return (Sprite)heliArrayGetValue(&group->sprites, i); } - -int groupOverlap(Group group, Sprite sprite) - { return groupCollideEx(group, sprite, TRUE, TRUE, 0); } -int groupCollide(Group group, Sprite sprite, double bounciness) - { return groupCollideEx(group, sprite, FALSE, FALSE, bounciness); } -int groupBounceOff(Group group, Sprite sprite, double bounciness) - { return groupCollideEx(group, sprite, FALSE, TRUE, bounciness); } -int groupPush(Group group, Sprite sprite, double bounciness) - { return groupCollideEx(group, sprite, TRUE, FALSE, bounciness); } -int groupCollideEx(Group group, Sprite sprite, int keepVelocityGroup, int keepVelocitySprite, double bounciness) { - int result = FALSE; - for(int i = 0; i < groupGetCount(group); ++i) - if (spriteCollideEx(groupGet(group, i), sprite, keepVelocityGroup, keepVelocitySprite, bounciness)) - result = TRUE; - return result; -} - -int groupOverlapGroup(Group a, Group b) - { return groupCollideGroupEx(a, b, TRUE, TRUE, 0); } -int groupCollideGroup(Group a, Group b, double bounciness) - { return groupCollideGroupEx(a, b, FALSE, FALSE, bounciness); } -int groupBounceOffGroup(Group group, Group other, double bounciness) - { return groupCollideGroupEx(group, other, FALSE, TRUE, bounciness); } -int groupPushGroup(Group group, Group other, double bounciness) - { return groupCollideGroupEx(group, other, TRUE, FALSE, bounciness); } -int groupCollideGroupEx(Group a, Group b, int keepVelocityA, int keepVelocityB, double bounciness) { - int result = FALSE; - for(int i = 0; i < groupGetCount(b); ++i) - if (groupCollideEx(a, groupGet(b, i), keepVelocityA, keepVelocityB, bounciness)) - result = TRUE; - return result; -} - -double groupGetMinDepth(Group group) { - if (groupGetCount(group) <= 0) return 0; - double md = spriteGetDepth(groupGet(group, 0)); - for(int i = 1; i < groupGetCount(group); ++i) { - double d = spriteGetDepth(groupGet(group, i)); - if (d < md) md = d; - } - return md; -} - -double groupGetMaxDepth(Group group) { - if (groupGetCount(group) <= 0) return 0; - double md = spriteGetDepth(groupGet(group, 0)); - for(int i = 1; i < groupGetCount(group); ++i) { - double d = spriteGetDepth(groupGet(group, i)); - if (d > md) md = d; - } - return md; -} - - -static void foreachInt(Group group, int value, HeliSpriteEashInt func) - { for(int i = 0; i < groupGetCount(group); ++i) func(groupGet(group, i), value); } -static void foreachDouble(Group group, double value, HeliSpriteEashDouble func) - { for(int i = 0; i < groupGetCount(group); ++i) func(groupGet(group, i), value); } -static void foreachString(Group group, const char *value, HeliSpriteEashString func) - { for(int i = 0; i < groupGetCount(group); ++i) func(groupGet(group, i), value); } - - -void groupDestroyTimerEach(Group group, double lifetime) - { foreachDouble(group, lifetime, &spriteDestroyTimer); } - -void groupSetVisibleEach(Group group, int visible) - { foreachInt(group, visible, &spriteSetVisible); } -void groupSetWidthEach(Group group, double width) - { foreachDouble(group, width, &spriteSetWidth); } -void groupSetHeightEach(Group group, double height) - { foreachDouble(group, height, &spriteSetHeight); } -void groupSetDepthEach(Group group, double depth) - { foreachDouble(group, depth, &spriteSetDepth); } -void groupSetVelocityXEach(Group group, double x) - { foreachDouble(group, x, &spriteSetVelocityX); } -void groupSetVelocityYEach(Group group, double y) - { foreachDouble(group, y, &spriteSetVelocityY); } -void groupSetRotateToDirectionEach(Group group, int rotateToDirection) - { foreachInt(group, rotateToDirection, &spriteSetRotateToDirection); } -void groupSetRotationEach(Group group, double rotation) - { foreachDouble(group, rotation, &spriteSetRotation); } -void groupSetRotationSpeedEach(Group group, double rotationSpeed) - { foreachDouble(group, rotationSpeed, &spriteSetRotationSpeed); } -void groupSetScaleEach(Group group, double scale) - { foreachDouble(group, scale, &spriteSetScale); } -void groupSetMirrorXEach(Group group, int mirrorX) - { foreachInt(group, mirrorX, &spriteSetMirrorX); } -void groupSetMirrorYEach(Group group, int mirrorY) - { foreachInt(group, mirrorY, &spriteSetMirrorY); } -void groupSetAnimationEach(Group group, const char *path) - { foreachString(group, path, &spriteSetAnimation); } -void groupSetShapeColorEach(Group group, const char *color) - { foreachString(group, color, &spriteSetShapeColor); } -void groupSetTintColorEach(Group group, const char *color) - { foreachString(group, color, &spriteSetTintColor); } - -void groupPointToEach(Group group, double x, double y) { - for(int i = 0; i < groupGetCount(group); ++i) - spritePointTo(groupGet(group, i), x, y); -} - -void groupSetSpeedAndDirectionEach(Group group, double speed, double angle) { - for(int i = 0; i < groupGetCount(group); ++i) - spriteSetSpeedAndDirection(groupGet(group, i), speed, angle); -} - -void groupSetVelocityEach(Group group, double x, double y) { - for(int i = 0; i < groupGetCount(group); ++i) - spriteSetVelocityXY(groupGet(group, i), x, y); -} - -void groupSetColliderEachEx(Group group, Collider type, double xOffset, double yOffset, - double widthOrRadius, double height, double rotationOffset) -{ - for(int i = 0; i < groupGetCount(group); ++i) - spriteSetColliderEx(groupGet(group, i), type, xOffset, yOffset, - widthOrRadius, height, rotationOffset); -} - -void groupSetColliderEach(Group group, Collider type, double xOffset, double yOffset) - { groupSetColliderEachEx(group, type, xOffset, yOffset, -1, -1, 0); } diff --git a/helianthus/group.h b/helianthus/group.h deleted file mode 100644 index 241577c..0000000 --- a/helianthus/group.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef HELI_GROUP_H -#define HELI_GROUP_H - - -#include "sprite.h" - - -typedef struct _Group *Group; - - -Group createGroup(); -Group createEdgesGroup(); -Group createEdgesGroupEx(double x1, double y1, double x2, double y2, double borderWidth); - -void groupDestroy(Group group); -void groupDestroyEx(Group group, int destroySprites); - -void groupAdd(Group group, Sprite sprite); -void groupRemove(Group group, Sprite sprite); -void groupClear(Group group); -void groupClearEx(Group group, int destroySprites); -int groupContains(Group group, Sprite sprite); -int groupGetCount(Group group); -Sprite groupGet(Group group, int i); - -int groupOverlap(Group group, Sprite sprite); -int groupCollide(Group group, Sprite sprite, double bounciness); -int groupBounceOff(Group group, Sprite sprite, double bounciness); -int groupPush(Group group, Sprite sprite, double bounciness); -int groupCollideEx(Group group, Sprite sprite, int keepVelocityGroup, int keepVelocitySprite, double bounciness); - -int groupOverlapGroup(Group a, Group b); -int groupCollideGroup(Group a, Group b, double bounciness); -int groupBounceOffGroup(Group group, Group other, double bounciness); -int groupPushGroup(Group group, Group other, double bounciness); -int groupCollideGroupEx(Group a, Group b, int keepVelocityA, int keepVelocityB, double bounciness); - -double groupGetMinDepth(Group group); -double groupGetMaxDepth(Group group); - -void groupDestroyEach(Group group); -void groupDestroyTimerEach(Group group, double lifetime); - -void groupSetVisibleEach(Group group, int visible); -void groupSetWidthEach(Group group, double width); -void groupSetHeightEach(Group group, double height); -void groupSetDepthEach(Group group, double depth); -void groupSetVelocityXEach(Group group, double x); -void groupSetVelocityYEach(Group group, double y); -void groupSetVelocityEach(Group group, double x, double y); -void groupSetLifetimeEach(Group group, double lifetime); -void groupSetRotateToDirectionEach(Group group, int rotateToDirection); -void groupSetRotationEach(Group group, double rotation); -void groupSetRotationSpeedEach(Group group, double rotationSpeed); -void groupSetScaleEach(Group group, double scale); -void groupSetMirrorXEach(Group group, int mirrorX); -void groupSetMirrorYEach(Group group, int mirrorY); -void groupPointToEach(Group group, double x, double y); -void groupSetSpeedAndDirectionEach(Group group, double speed, double angle); -void groupSetAnimationEach(Group group, const char *path); -void groupSetShapeColorEach(Group group, const char *color); -void groupSetTintColorEach(Group group, const char *color); -void groupSetColliderEach(Group group, Collider type, double xOffset, double yOffset); -void groupSetColliderEachEx(Group group, Collider type, double xOffset, double yOffset, - double widthOrRadius, double height, double rotationOffset); - - -#endif diff --git a/helianthus/helianthus.h b/helianthus/helianthus.h deleted file mode 100644 index 9288436..0000000 --- a/helianthus/helianthus.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef HELI_HELIANTUS_H -#define HELI_HELIANTUS_H - - -#include "common.h" -#include "world.h" -#include "sprite.h" -#include "group.h" -#include "drawing.h" - - -#endif diff --git a/helianthus/main.c b/helianthus/main.c deleted file mode 100644 index d7c4f64..0000000 --- a/helianthus/main.c +++ /dev/null @@ -1,49 +0,0 @@ - -#include - -#include "helianthus.h" - -Sprite ball, brick1, brick2; -Sound beep; - -void init() { - ball = createSpriteEx(200, 200, 64, 64); - spriteSetAnimation(ball, "data/sprite/breadball.png"); - - brick1 = createSpriteEx(200-32, 200+64, 64, 64); - spriteSetAnimation(brick1, "data/sprite/bricks.png"); - spriteSetTintColor(brick1, rgb(0, 0, 1)); - - brick2 = createSpriteEx(200+32, 200+64, 64, 64); - spriteSetAnimation(brick2, "data/sprite/bricks.png"); - spriteSetTintColor(brick2, rgba(1, 0, 1, 0.5)); - - beep = createSound("data/sound/beep.ogg"); -} - -void draw() { - double dt = worldGetTimeStep(); - double speed = 100; - - spritePointTo(ball, mouseX(), mouseY()); - - double x = spriteGetX(ball); - double y = spriteGetY(ball); - if (keyDown("left")) spriteSetX(ball, x - speed*dt); - if (keyDown("right")) spriteSetX(ball, x + speed*dt); - if (keyDown("up")) spriteSetY(ball, y - speed*dt); - if (keyDown("down")) spriteSetY(ball, y + speed*dt); - - spriteSetSpeedAndDirection(ball, speed/2, spriteGetRotation(ball)); - - if (mouseWentDown("left")) soundPlay(beep, FALSE); - - drawSprites(); -} - -int main() { - worldSetInit(&init); - worldSetDraw(&draw); - worldRun(); - return 0; -} diff --git a/helianthus/private.h b/helianthus/private.h deleted file mode 100644 index 34b7d47..0000000 --- a/helianthus/private.h +++ /dev/null @@ -1,120 +0,0 @@ - -#include -#include -#include -#include -#include -#include - -#include - -#include "common.h" -#include "sprite.h" - - -// globals - -extern cairo_t *heliCairo; - - -// string - -char* heliStringCopy(const char *x); -char* heliStringConcat3(const char *a, const char *b, const char *c); -int heliStringEndsWithLowcase(const char *s, const char *tail); -void heliLowercase(char *x); -void heliParseColor(const char *x, double *color); - - -// pointer array - -typedef void (*HeliFreeCallback)(void*); - -typedef struct _HeliPair { - void *key; - void *value; - HeliFreeCallback freeKey; - HeliFreeCallback freeValue; -} HeliPair; - -typedef struct _HeliArray { - HeliPair *items; - int count; - int allocated; -} HeliArray; - -void heliPairInit(HeliPair *p); -void heliPairDestroy(HeliPair *p); - -void heliArrayInit(HeliArray *a); -void heliArrayClear(HeliArray *a); -void heliArrayDestroy(HeliArray *a); -HeliPair* heliArrayGet(HeliArray *a, int i); -void* heliArrayGetKey(HeliArray *a, int i); -void* heliArrayGetValue(HeliArray *a, int i); -HeliPair* heliArrayInsert(HeliArray *a, int i, void *v, HeliFreeCallback fv); -HeliPair* heliArrayInsertPair(HeliArray *a, int i, void *k, void *v, HeliFreeCallback fk, HeliFreeCallback fv); -void heliArrayRemove(HeliArray *a, int i); - -HeliPair* heliStringmapFind(HeliArray *a, const char *k, int *gtOrEqIndex); -HeliPair* heliStringmapGet(HeliArray *a, const char *k); -HeliPair* heliStringmapAdd(HeliArray *a, const char *k, void *v, HeliFreeCallback fv); -int heliStringmapRemove(HeliArray *a, const char *k); - - -// animation - -typedef struct _HeliAnimation { - char *path; - HeliArray frames; - int refcount; -} HeliAnimation; - -HeliAnimation *heliCreateAnimation(const char *path); -void heliAnimationUnref(HeliAnimation *a); -void heliAnimationFinish(); - - -// collider - -typedef struct _HeliCollider { - Collider type; - double x; - double y; - double radius; - double width; - double height; - double rotation; -} HeliCollider; - -int heliCheckCollision(HeliCollider *a, HeliCollider *b, double *normX, double *normY); -int heliPointCollision(HeliCollider *c, double x, double y); - - -// sprite - -typedef void (*HeliSpriteEashInt)(Sprite, int); -typedef void (*HeliSpriteEashDouble)(Sprite, double); -typedef void (*HeliSpriteEashString)(Sprite, const char*); -HeliArray* heliSpriteGetGroups(Sprite sprite); -void heliSpriteUpdate(double dt); -void heliSpriteFinish(); - - -// drawing - -void heliDrawingClearFrame(cairo_t *cr); -void heliDrawingPrepareFrame(); -void heliDrawingFinish(); - - -// sound - -void heliSoundUpdate(); -void heliSoundFinish(); - - -// test - -void heliDoTests(); - diff --git a/helianthus/sound.c b/helianthus/sound.c deleted file mode 100644 index aa36d92..0000000 --- a/helianthus/sound.c +++ /dev/null @@ -1,163 +0,0 @@ - -#include - -#include -#include - -#include "private.h" -#include "world.h" - - -#define HELI_SOUND_CHANNELS 64 - - -typedef struct _HeliSoundInstance { - char *path; - Mix_Chunk *chunk; - int refcount; -} HeliSoundInstance; - -typedef struct _HeliSoundTrashItem { - Mix_Chunk *chunk; - gint64 time; -} HeliSoundTrashItem; - -struct _Sound { - HeliSoundInstance *instance; -}; - - -static int initialized; -static int ready; - -static HeliArray cache; -static Sound channels[HELI_SOUND_CHANNELS]; - -static HeliSoundTrashItem *trash; -static size_t trashSize; - - -static void trashAdd(Mix_Chunk *chunk) { - if (!chunk) return; - - gint64 time = g_get_monotonic_time() + 1000000; - for(int i = 0; i < trashSize; ++i) - if (!trash[i].chunk) - { trash[i].chunk = chunk; trash[i].time = time; return; } - - size_t prevSize = trashSize; - trashSize += trashSize/4 + 32; - trash = realloc(trash, trashSize); - memset(&trash[prevSize], 0, (trashSize - prevSize)*sizeof(*trash)); - - trash[prevSize].chunk = chunk; - trash[prevSize].time = time; -} - -static void trashProcess() { - gint64 time = g_get_monotonic_time(); - for(int i = 0; i < trashSize; ++i) - if (trash[i].chunk && trash[i].time <= time) - { Mix_FreeChunk(trash[i].chunk); trash[i].chunk = NULL; } -} - -static void trashDestroy() { - for(int i = 0; i < trashSize; ++i) - if (trash[i].chunk) Mix_FreeChunk(trash[i].chunk); - trashSize = 0; - free(trash); - trash = NULL; -} - - -static void init() { - if (initialized) return; - if (SDL_Init(SDL_INIT_AUDIO) == 0) { - int loaded = Mix_Init(MIX_INIT_OGG | MIX_INIT_FLAC | MIX_INIT_MP3); - if (!(loaded & MIX_INIT_OGG )) fprintf(stderr, "helianthus: cannot initialize OGG support\n"); - if (!(loaded & MIX_INIT_FLAC)) fprintf(stderr, "helianthus: cannot initialize FLAC support\n"); - if (!(loaded & MIX_INIT_MP3 )) fprintf(stderr, "helianthus: cannot initialize MP3 support\n"); - ready = Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 1024) == 0; - if (!ready) fprintf(stderr, "helianthus: cannot open audio device\n"); - } - initialized = TRUE; -} - -static void deinit() { - if (!initialized) return; - heliArrayDestroy(&cache); - if (ready) Mix_CloseAudio(); - trashDestroy(); - Mix_Quit(); - SDL_Quit(); - for(int i = 0; i < HELI_SOUND_CHANNELS; ++i) channels[i] = NULL; - ready = FALSE; - initialized = FALSE; -} - - -static HeliSoundInstance* load(const char *path) { - HeliSoundInstance *s = calloc(1, sizeof(*s)); - s->path = heliStringCopy(path); - if (ready) { - s->chunk = Mix_LoadWAV(path); - if (!s->chunk) - fprintf(stderr, "helianthus: cannot load sound file: %s\n", path); - } - return s; -} - -static void unload(HeliSoundInstance *s) { - assert(!s->refcount); - free(s->path); - trashAdd(s->chunk); - free(s); -} - - -Sound createSound(const char *path) { - init(); - HeliPair *item = heliStringmapGet(&cache, path); - if (!item) item = heliStringmapAdd(&cache, path, load(path), (HeliFreeCallback)&unload); - HeliSoundInstance *s = (HeliSoundInstance*)item->value; - ++s->refcount; - - Sound sound = calloc(1, sizeof(*sound)); - sound->instance = s; - return sound; -} - -void soundDestroy(Sound sound) { - soundStop(sound); - if (--sound->instance->refcount <= 0) - heliStringmapRemove(&cache, sound->instance->path); -} - - -void soundPlay(Sound sound, int loop) { - if (!ready) return; - if (sound->instance->chunk) { - int channel = Mix_PlayChannel(-1, sound->instance->chunk, loop ? -1 : 0); - assert(channel < HELI_SOUND_CHANNELS); - if (channel >= 0 && channel < HELI_SOUND_CHANNELS) - channels[channel] = sound; - } -} - -void soundStop(Sound sound) { - for(int i = 0; i < HELI_SOUND_CHANNELS; ++i) { - if (channels[i] == sound) { - Mix_Pause(i); - Mix_HaltChannel(i); - channels[i] = NULL; - } - } -} - - -void heliSoundUpdate() - { trashProcess(); } - -void heliSoundFinish() - { deinit(); } - diff --git a/helianthus/sprite.c b/helianthus/sprite.c deleted file mode 100644 index eb14663..0000000 --- a/helianthus/sprite.c +++ /dev/null @@ -1,441 +0,0 @@ - -#include - -#include "private.h" - -#include "sprite.h" -#include "world.h" -#include "group.h" - -struct _Sprite { - double x; - double y; - double rotation; - double width; - double height; - double scale; - - double vx; - double vy; - double rotationSpeed; - - HeliCollider collider; - double bounciness; - - int rotateToDirection; - int mirrorX; - int mirrorY; - double depth; - double lifeTime; - int visible; - int debug; - - double shapeColor[4]; - double tintColor[4]; - - int playing; - int frame; - - HeliArray groups; - - HeliAnimation *animation; -}; - -static HeliArray sprites; -static HeliArray spritesSorted; - - -static void prepareCollider(Sprite sprite, HeliCollider *collider) { - collider->type = sprite->collider.type; - collider->x = sprite->collider.x + sprite->x; - collider->y = sprite->collider.y + sprite->y; - collider->width = sprite->collider.width >= 0 ? sprite->collider.width : sprite->width; - collider->height = sprite->collider.height >= 0 ? sprite->collider.height : sprite->height; - collider->radius = sprite->collider.radius >= 0 ? sprite->collider.radius : collider->width; - collider->rotation = sprite->collider.rotation + sprite->rotation; -} - - - -Sprite createSpriteEx(double x, double y, double width, double height) { - Sprite s = calloc(1, sizeof(*s)); - s->x = x; - s->y = y; - s->width = width; - s->height = height; - - s->scale = 1; - s->collider.type = COLLIDER_RECTANGLE; - s->collider.width = s->collider.height = s->collider.radius = -1; - - s->mirrorX = 1; - s->mirrorY = 1; - s->lifeTime = -1; - s->visible = TRUE; - - s->shapeColor[0] = s->shapeColor[1] = s->shapeColor[2] = 0.5; - s->shapeColor[3] = 1; - s->tintColor[0] = s->tintColor[1] = s->tintColor[2] = s->tintColor[3] = 1; - - heliArrayInsert(&sprites, -1, s, NULL); - heliArrayInsert(&spritesSorted, -1, s, NULL); - - return s; -} - -Sprite createSprite(double x, double y) - { return createSpriteEx(x, y, 100, 100); } - -void spriteDestroy(Sprite sprite) { - while(sprite->groups.count > 0) - groupRemove((Group)sprite->groups.items[0].value, sprite); - for(int i = 0; i < sprites.count; ++i) - if (sprites.items[i].value == sprite) - { heliArrayRemove(&sprites, i); break; } - for(int i = 0; i < spritesSorted.count; ++i) - if (spritesSorted.items[i].value == sprite) - { heliArrayRemove(&spritesSorted, i); break; } - if (sprite->animation) - heliAnimationUnref(sprite->animation); - free(sprite); -} - -void spriteDestroyTimer(Sprite sprite, double lifeTime) { - if (lifeTime <= 0) { spriteDestroy(sprite); return; } - sprite->lifeTime = lifeTime; -} - -double spriteGetX(Sprite sprite) { return sprite->x; } -void spriteSetX(Sprite sprite, double x) { sprite->x = x; } - -double spriteGetY(Sprite sprite) { return sprite->y; } -void spriteSetY(Sprite sprite, double y) { sprite->y = y; } - -double spriteGetVelocityX(Sprite sprite) { return sprite->vx; } -void spriteSetVelocityX(Sprite sprite, double x) - { spriteSetVelocityXY(sprite, x, sprite->vy); } - -double spriteGetVelocityY(Sprite sprite) { return sprite->vy; } -void spriteSetVelocityY(Sprite sprite, double y) - { spriteSetVelocityXY(sprite, sprite->vx, y); } - -double spriteGetScale(Sprite sprite) { return sprite->scale; } -void spriteSetScale(Sprite sprite, double scale) { sprite->scale = scale; } - -double spriteGetWidth(Sprite sprite) { return sprite->width; } -void spriteSetWidth(Sprite sprite, double width) - { sprite->width = width > 0 ? width : 0; } - -double spriteGetHeight(Sprite sprite) { return sprite->height; } -void spriteSetHeight(Sprite sprite, double height) - { sprite->height = height > 0 ? height : 0; } - -int spriteGetRotateToDirection(Sprite sprite) { return sprite->rotateToDirection; } -void spriteSetRotateToDirection(Sprite sprite, int rotateToDirection) { - if (rotateToDirection) { - sprite->rotateToDirection = TRUE; - sprite->rotation = spriteGetDirection(sprite); - } else { - sprite->rotateToDirection = FALSE; - } -} - -double spriteGetRotation(Sprite sprite) { return sprite->rotation; } -void spriteSetRotation(Sprite sprite, double rotation) - { if (!sprite->rotateToDirection) sprite->rotation = rotation; } - -double spriteGetRotationSpeed(Sprite sprite) { return sprite->rotationSpeed; } -void spriteSetRotationSpeed(Sprite sprite, double rotationSpeed) - { sprite->rotationSpeed = rotationSpeed; } - -int spriteGetMirrorX(Sprite sprite) { return sprite->mirrorX; } -void spriteSetMirrorX(Sprite sprite, int mirrorX) - { sprite->mirrorX = mirrorX < 0 ? -1 : 1; } - -int spriteGetMirrorY(Sprite sprite) { return sprite->mirrorY; } -void spriteSetMirrorY(Sprite sprite, int mirrorY) - { sprite->mirrorY = mirrorY < 0 ? -1 : 1; } - -double spriteGetDepth(Sprite sprite) { return sprite->depth; } -void spriteSetDepth(Sprite sprite, double depth) - { sprite->depth = depth; } - -int spriteGetVisible(Sprite sprite) { return sprite->visible; } -void spriteSetVisible(Sprite sprite, int visible) - { sprite->visible = visible != FALSE; } - -int spriteGetDebug(Sprite sprite) { return sprite->debug; } -void spriteSetDebug(Sprite sprite, int debug) - { sprite->debug = debug != FALSE; } - -int spriteOverlap(Sprite a, Sprite b) - { return spriteCollideEx(a, b, TRUE, TRUE, 0); } -int spriteCollide(Sprite a, Sprite b, double bounciness) - { return spriteCollideEx(a, b, FALSE, FALSE, bounciness); } -int spriteBounceOff(Sprite sprite, Sprite other, double bounciness) - { return spriteCollideEx(sprite, other, FALSE, TRUE, bounciness); } -int spritePush(Sprite sprite, Sprite other, double bounciness) - { return spriteCollideEx(sprite, other, TRUE, FALSE, bounciness); } - -int spriteCollideEx(Sprite a, Sprite b, int keepVelocityA, int keepVelocityB, double bounciness) { - #warning "TODO: spriteCollision: not implemented yet" - return FALSE; -} - - -double spriteGetBounciness(Sprite sprite) { return sprite->bounciness; } -void spriteSetBounciness(Sprite sprite, double bounciness) - { sprite->bounciness = bounciness > 0 ? bounciness : 0; } - -void spriteSetColliderEx(Sprite sprite, Collider type, double xOffset, double yOffset, - double widthOrRadius, double height, double rotationOffset) -{ - sprite->collider.type = type; - sprite->collider.x = xOffset; - sprite->collider.y = yOffset; - sprite->collider.width = sprite->collider.radius = widthOrRadius; - sprite->collider.height = height; - sprite->collider.rotation = rotationOffset; -} - -void spriteSetCollider(Sprite sprite, Collider type, double xOffset, double yOffset) - { spriteSetColliderEx(sprite, type, xOffset, yOffset, -1, -1, 0); } - -void spriteSetAnimation(Sprite sprite, const char *path) { - HeliAnimation *prev = sprite->animation; - sprite->animation = heliCreateAnimation(path); - if (prev) heliAnimationUnref(prev); - spriteSetFrame(sprite, sprite->frame); -} - -void spritePlay(Sprite sprite) { sprite->playing = TRUE; } -void spritePause(Sprite sprite) { sprite->playing = FALSE; } -void spriteNextFrame(Sprite sprite) { spriteSetFrame(sprite, sprite->frame + 1); } - -void spriteSetFrame(Sprite sprite, int frame) { - sprite->frame = frame >= 0 && sprite->animation && sprite->animation->frames.count > 0 - ? frame % sprite->animation->frames.count : 0; -} - -void spriteSetShapeColor(Sprite sprite, const char *color) - { heliParseColor(color, sprite->shapeColor); } -void spriteSetTintColor(Sprite sprite, const char *color) - { heliParseColor(color, sprite->tintColor); } - -void spriteSetVelocityXY(Sprite sprite, double x, double y) { - sprite->vx = x; - sprite->vy = y; - if (sprite->rotateToDirection) - sprite->rotation = spriteGetDirection(sprite); -} - -void spriteSetSpeedAndDirection(Sprite sprite, double speed, double angle) { - double a = angle*(PI/180); - spriteSetVelocityXY(sprite, cos(a)*speed, sin(a)*speed); -} - -double spriteGetSpeed(Sprite sprite) - { return sqrt(sprite->vx*sprite->vx + sprite->vy*sprite->vy); } -double spriteGetDirection(Sprite sprite) { - return fabs(sprite->vx) > 1e-6 || fabs(sprite->vy) > 1e-6 - ? atan2(sprite->vy, sprite->vx)*(180/PI) : 0; -} -void spritePointTo(Sprite sprite, double x, double y) - { spriteSetRotation( sprite, atan2(y - sprite->y, x - sprite->x)*(180/PI) ); } - -double spriteGetScaledWidth(Sprite sprite) - { return sprite->width * sprite->scale; } -double spriteGetScaledHeight(Sprite sprite) - { return sprite->height * sprite->scale; } - - - -int worldGetSpriteCount() - { return sprites.count; } - -Sprite worldGetSprite(int i) - { return (Sprite)heliArrayGetValue(&sprites, i); } - - -static void drawSpriteDebug(cairo_t *cr, Sprite s) { - cairo_save(cr); - cairo_set_line_width(cr, 0.5); - 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); - - double hw = s->width*0.5; - double hh = s->height*0.5; - - cairo_set_source_rgba(cr, 0, 0, 0, 0.75); - 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_stroke(cr); - - cairo_move_to(cr, -hw*0.25, 0); - cairo_line_to(cr, hw*0.25, 0); - cairo_move_to(cr, 0, -hh*0.25); - cairo_line_to(cr, 0, hh*0.25); - cairo_stroke(cr); - - char buf[1024]; - snprintf(buf, sizeof(buf)-1, "%lf", s->depth); - - double s1 = hw*0.25; - double s2 = hh*0.5; - cairo_set_font_size(cr, s1 < s2 ? s1 : s2); - - cairo_move_to(cr, -hw*0.9, -hh*0.5); - 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); - - double hw = s->width*0.5; - double hh = s->height*0.5; - - 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) > 1e-5 - || fabs(s->tintColor[1] - 1) > 1e-5 - || fabs(s->tintColor[2] - 1) > 1e-5; - int alpha = fabs(a) > 1e-5; - int visible = fabs(a) > 1e-5; - - if (visible) { - if (tint) cairo_push_group(cr); - - 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 > 1e-6 && ihh > 1e-6) { - 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); - } - } 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); - } - - 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); - } - } - - cairo_restore(cr); -} - -void drawSprites() { - // sort - int found = TRUE; - while(found) { - found = FALSE; - // forward - for(int i = 1; i < spritesSorted.count; ++i) { - if ( ((Sprite)spritesSorted.items[i-1].value)->depth - < ((Sprite)spritesSorted.items[i ].value)->depth ) - { - void *x = spritesSorted.items[i].value; - spritesSorted.items[i].value = spritesSorted.items[i-1].value; - spritesSorted.items[i-1].value = x; - found = TRUE; - } - } - if (!found) break; - // backward - found = FALSE; - for(int i = spritesSorted.count - 1; i > 0; --i) { - if ( ((Sprite)spritesSorted.items[i-1].value)->depth - < ((Sprite)spritesSorted.items[i ].value)->depth ) - { - void *x = spritesSorted.items[i].value; - spritesSorted.items[i].value = spritesSorted.items[i-1].value; - spritesSorted.items[i-1].value = x; - found = TRUE; - } - } - } - - 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); - } - } -} - -int mouseIsOver(Sprite sprite) { - HeliCollider collider; - prepareCollider(sprite, &collider); - return heliPointCollision(&collider, mouseX(), mouseY()); -} - -void heliSpriteUpdate(double dt) { - // auto-remove - for(int i = sprites.count - 1; i > 0; --i) { - Sprite s = (Sprite)sprites.items[i].value; - if (s->lifeTime >= 0) { - s->lifeTime -= dt; - if (s->lifeTime <= 1e-5) - spriteDestroy(s); - } - } - - // update - for(int i = 0; i < sprites.count; ++i) { - Sprite s = (Sprite)sprites.items[i].value; - s->x += s->vx*dt; - s->y += s->vy*dt; - if (!s->rotateToDirection) - s->rotation += s->rotationSpeed*dt; - if (s->playing) spriteNextFrame(s); - } -} - -HeliArray* heliSpriteGetGroups(Sprite sprite) - { return &sprite->groups; } - -void heliSpriteFinish() { - while(worldGetSpriteCount()) - { spriteDestroy(worldGetSprite(0)); } - heliArrayDestroy(&sprites); - heliArrayDestroy(&spritesSorted); -} - - diff --git a/helianthus/sprite.h b/helianthus/sprite.h deleted file mode 100644 index aa5f28c..0000000 --- a/helianthus/sprite.h +++ /dev/null @@ -1,97 +0,0 @@ -#ifndef HELI_SPRITE_H -#define HELI_SPRITE_H - - -#include "common.h" - -typedef struct _Sprite *Sprite; - - -typedef enum _Collider { - COLLIDER_RECTANGLE, - COLLIDER_CIRCLE -} Collider; - -Sprite createSprite(double x, double y); -Sprite createSpriteEx(double x, double y, double width, double height); - -void spriteDestroy(Sprite sprite); -void spriteDestroyTimer(Sprite sprite, double lifetime); - -double spriteGetX(Sprite sprite); -void spriteSetX(Sprite sprite, double x); - -double spriteGetY(Sprite sprite); -void spriteSetY(Sprite sprite, double y); - -double spriteGetVelocityX(Sprite sprite); -void spriteSetVelocityX(Sprite sprite, double x); - -double spriteGetVelocityY(Sprite sprite); -void spriteSetVelocityY(Sprite sprite, double y); - -double spriteGetScale(Sprite sprite); -void spriteSetScale(Sprite sprite, double scale); - -double spriteGetWidth(Sprite sprite); -void spriteSetWidth(Sprite sprite, double width); - -double spriteGetHeight(Sprite sprite); -void spriteSetHeight(Sprite sprite, double height); - -int spriteGetRotateToDirection(Sprite sprite); -void spriteSetRotateToDirection(Sprite sprite, int rotateToDirection); - -double spriteGetRotation(Sprite sprite); -void spriteSetRotation(Sprite sprite, double rotation); - -double spriteGetRotationSpeed(Sprite sprite); -void spriteSetRotationSpeed(Sprite sprite, double rotationSpeed); - -int spriteGetMirrorX(Sprite sprite); -void spriteSetMirrorX(Sprite sprite, int mirrorX); - -int spriteGetMirrorY(Sprite sprite); -void spriteSetMirrorY(Sprite sprite, int mirrorY); - -double spriteGetDepth(Sprite sprite); -void spriteSetDepth(Sprite sprite, double depth); - -int spriteGetVisible(Sprite sprite); -void spriteSetVisible(Sprite sprite, int visible); - -int spriteGetDebug(Sprite sprite); -void spriteSetDebug(Sprite sprite, int debug); - -int spriteOverlap(Sprite a, Sprite b); -int spriteCollide(Sprite a, Sprite b, double bounciness); -int spriteBounceOff(Sprite sprite, Sprite other, double bounciness); -int spritePush(Sprite sprite, Sprite other, double bounciness); -int spriteCollideEx(Sprite a, Sprite b, int keepVelocityA, int keepVelocityB, double bounciness); - -double spriteGetBounciness(Sprite sprite); -void spriteSetBounciness(Sprite sprite, double bounciness); - -void spriteSetCollider(Sprite sprite, Collider type, double xOffset, double yOffset); -void spriteSetColliderEx(Sprite sprite, Collider type, double xOffset, double yOffset, - double widthOrRadius, double height, double rotationOffset); - -void spriteSetAnimation(Sprite sprite, const char *path); -void spritePlay(Sprite sprite); -void spritePause(Sprite sprite); -void spriteNextFrame(Sprite sprite); -void spriteSetFrame(Sprite sprite, int frame); -void spriteSetShapeColor(Sprite sprite, const char *color); -void spriteSetTintColor(Sprite sprite, const char *color); - -void spriteSetVelocityXY(Sprite sprite, double x, double y); -void spriteSetSpeedAndDirection(Sprite sprite, double speed, double angle); -double spriteGetSpeed(Sprite sprite); -double spriteGetDirection(Sprite sprite); -void spritePointTo(Sprite sprite, double x, double y); - -double spriteGetScaledWidth(Sprite sprite); -double spriteGetScaledHeight(Sprite sprite); - - -#endif diff --git a/helianthus/test.c b/helianthus/test.c deleted file mode 100644 index 713f2cd..0000000 --- a/helianthus/test.c +++ /dev/null @@ -1,60 +0,0 @@ - -#include "private.h" - -//#define HELI_DO_TESTS - -#ifndef HELI_DO_TESTS -void heliDoTests() { } -#else - - -static printStringset(HeliArray *a) { - for(int i = 0; i < a->count; ++i) - printf(" %s\n", (const char*)a->items[i].key); -} - -static void testStringSet() { - printf("-- testStringSet\n"); - - static char *strings[] = { - "hello", - "apple", - "orange", - "pineapple", - "potato", - "carrot", - "grape", - "cucumber" - }; - - HeliArray a = {}; - for(int i = 0; i < (int)(sizeof(strings)/sizeof(*strings)); ++i) - heliStringmapAdd(&a, strings[i], NULL, NULL); - - printf(" fill:\n"); - printStringset(&a); - - printf("\n -remove %s:\n", "hello"); - heliStringmapRemove(&a, "hello"); - printStringset(&a); - - printf("\n -remove %s:\n", "apple"); - heliStringmapRemove(&a, "apple"); - printStringset(&a); - - printf("\n -remove %s:\n", "potato"); - heliStringmapRemove(&a, "potato"); - printStringset(&a); - - heliArrayDestroy(&a); - - printf("-- end\n"); -} - -void heliDoTests() { - printf("---- doTests\n"); - testStringSet(); - printf("---- end\n"); -} - -#endif diff --git a/helianthus/world.c b/helianthus/world.c deleted file mode 100644 index a7b9896..0000000 --- a/helianthus/world.c +++ /dev/null @@ -1,322 +0,0 @@ - -#include -#include -#include - -#include "private.h" - -#include "world.h" - - -static GtkWidget *window; - -static int started; -static int stopped; -static unsigned long long frameCount; -static gint64 startTime; -static gint64 currentTime; - -static Callback initCallback; -static Callback drawCallback; - -static cairo_surface_t *cairoSurface; -static int width = 400; -static int height = 400; -static double frameRate = 30; - -static int cameraEnabled; -static double cameraX; -static double cameraY; -static double cameraZoom = 1; - -static HeliArray keysPressed; -static HeliArray keysPressedInFrame; -static HeliArray keysReleasedInFrame; -static HeliArray buttonsPressed; -static HeliArray buttonsPressedInFrame; -static HeliArray buttonsReleasedInFrame; - -static int mouseMovedInFrame; -static double _mouseX; -static double _mouseY; - -static char* buttonNames[] = { - "left", - "middle", - "right" }; -static int buttonsCount = (int)(sizeof(buttonNames)/sizeof(*buttonNames)); - - - -int keyDown(const char *code) - { return heliStringmapGet(&keysPressed, code) != NULL; } -int keyWentDown(const char *code) - { return heliStringmapGet(&keysPressedInFrame, code) != NULL; } -int keyWentUp(const char *code) - { return heliStringmapGet(&keysReleasedInFrame, code) != NULL; } - -int mouseDidMove() - { return mouseMovedInFrame; } -int mouseDown(const char *code) - { return heliStringmapGet(&buttonsPressed, code) != NULL; } -int mouseWentDown(const char *code) - { return heliStringmapGet(&buttonsPressedInFrame, code) != NULL; } -int mouseWentUp(const char *code) - { return heliStringmapGet(&buttonsReleasedInFrame, code) != NULL; } -double mouseX() - { return _mouseX; } -double mouseY() - { return _mouseY; } - -int mousePressedOver(Sprite sprite) - { return buttonsPressed.count && mouseIsOver(sprite); } - -static void resize(int w, int h) { - width = w > 200 ? w : 100; - height = h > 200 ? h : 200; - if (started) { - gtk_window_set_default_size(GTK_WINDOW(window), width, height); - gtk_widget_set_size_request(window, width, height); - } -} - -int worldGetWidth() - { return width; } -void worldSetWidth(int w) - { resize(w, height); } - -int worldGetHeight() - { return height; } -void worldSetHeight(int h) - { resize(width, h); } - -double worldGetFrameRate() - { return frameRate; } -void worldSetFrameRate(double frameRate) { - if (!(frameRate > 1)) frameRate = 1; - if (!(frameRate < 100)) frameRate = 100; -} - -double worldGetTimeStep() - { return 1/frameRate; } - -int worldGetFrameCount() - { return (int)frameCount; } -double worldGetSeconds() - { return started ? (currentTime - startTime)*1e-6 : 0.0; } - -void worldSetInit(Callback init) - { initCallback = init; } -void worldSetDraw(Callback draw) - { drawCallback = draw; } -void worldStop() - { if (started) stopped = TRUE; } - - -void cameraOn() - { cameraEnabled = TRUE; } -void cameraOff() - { cameraEnabled = FALSE; } -int cameraIsActive() - { return cameraEnabled; } - -double cameraMouseX() - { return _mouseX/cameraZoom + cameraX; } -double cameraMouseY() - { return _mouseY/cameraZoom + cameraY; } - -double cameraGetX() - { return cameraX; } -void cameraSetX(double x) - { cameraX = x; } - -double cameraGetY() - { return cameraY; } -void cameraSetY(double y) - { cameraY = y; } - -double cameraGetZoom() - { return cameraZoom; } -void cameraSetZoom(double x) - { cameraZoom = x > 1e-6 ? x : 1e-6; } - -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; - - 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 (doFrame) { - if (frameCount == 0) heliDoTests(); - - heliCairo = cairo_create(cairoSurface); - heliDrawingPrepareFrame(); - if (drawCallback) drawCallback(); - cairo_surface_flush(cairoSurface); - cairo_destroy(heliCairo); - heliCairo = NULL; - - currentTime += timeStep; - ++frameCount; - heliSpriteUpdate(dt); - heliSoundUpdate(); - - heliArrayClear(&keysPressedInFrame); - heliArrayClear(&keysReleasedInFrame); - heliArrayClear(&buttonsPressedInFrame); - heliArrayClear(&buttonsReleasedInFrame); - - if (stopped) g_idle_add_full(G_PRIORITY_DEFAULT, idle, NULL, NULL); - } - - gint64 delta = newTime - currentTime - 2000000; - if (delta > 0) { - startTime += delta; - currentTime += delta; - } - - heliDrawingClearFrame(cr); - cairo_save(cr); - cairo_set_source_surface(cr, cairoSurface, 0, 0); - cairo_paint(cr); - cairo_restore(cr); - - if (!stopped) gtk_widget_queue_draw(window); - - delta = currentTime - g_get_monotonic_time(); - if (delta > 100) g_usleep(delta - 100); - - 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); - heliStringmapAdd(&keysPressed, keyname, NULL, NULL); - heliStringmapAdd(&keysPressedInFrame, keyname, NULL, NULL); - free(keyname); - return TRUE; - case GDK_KEY_RELEASE: - keyname = heliStringCopy( gdk_keyval_name(event->key.keyval) ); - heliLowercase(keyname); - heliStringmapRemove(&keysPressed, keyname); - heliStringmapAdd(&keysReleasedInFrame, keyname, NULL, NULL); - free(keyname); - return TRUE; - case GDK_BUTTON_PRESS: - button = event->button.button; - if (button >= 1 && button <= buttonsCount) { - heliStringmapAdd(&buttonsPressed, buttonNames[button - 1], NULL, NULL); - heliStringmapAdd(&buttonsPressedInFrame, buttonNames[button - 1], NULL, NULL); - } - _mouseX = event->button.x; - _mouseY = event->button.y; - return TRUE; - case GDK_BUTTON_RELEASE: - button = event->button.button; - if (button >= 1 && button <= buttonsCount) { - heliStringmapRemove(&buttonsPressed, buttonNames[button - 1]); - heliStringmapAdd(&buttonsReleasedInFrame, buttonNames[button - 1], NULL, NULL); - } - _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_DELETE: - stopped = TRUE; - window = NULL; - break; - default: - break; - } - 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), FALSE); - gtk_window_set_default_size(GTK_WINDOW(window), width, height); - 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 ); - gtk_widget_set_size_request(window, width, height); - gtk_widget_show(window); - - currentTime = startTime = 0; - if (initCallback) initCallback(); - g_idle_add(idle, NULL); -} - -void worldRun() { - if (started) return; - started = TRUE; - stopped = FALSE; - - frameCount = 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); - - heliSpriteFinish(); - heliDrawingFinish(); - heliAnimationFinish(); - heliSoundFinish(); - - if (cairoSurface) cairo_surface_destroy(cairoSurface); - cairoSurface = NULL; - - heliArrayDestroy(&keysPressed); - heliArrayDestroy(&keysPressed); - heliArrayDestroy(&keysPressedInFrame); - heliArrayDestroy(&keysReleasedInFrame); - heliArrayDestroy(&buttonsPressed); - heliArrayDestroy(&buttonsPressedInFrame); - heliArrayDestroy(&buttonsReleasedInFrame); - - started = FALSE; -} diff --git a/helianthus/world.h b/helianthus/world.h deleted file mode 100644 index c01e67f..0000000 --- a/helianthus/world.h +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef HELI_WORLD_H -#define HELI_WORLD_H - - -#include "common.h" -#include "sprite.h" - -typedef void (*Callback)(); -typedef struct _Sound *Sound; - - -void drawSprites(); - -Sound createSound(const char *path); -void soundDestroy(Sound sound); -void soundPlay(Sound sound, int loop); -void soundStop(Sound sound); - -int keyDown(const char *code); -int keyWentDown(const char *code); -int keyWentUp(const char *code); - -int mouseDidMove(); -int mouseDown(const char *code); -int mouseWentDown(const char *code); -int mouseWentUp(const char *code); -double mouseX(); -double mouseY(); - -int mouseIsOver(Sprite sprite); -int mousePressedOver(Sprite sprite); - - -int worldGetSpriteCount(); -Sprite worldGetSprite(int i); - -int worldGetWidth(); -void worldSetWidth(int width); - -int worldGetHeight(); -void worldSetHeight(int height); - -double worldGetFrameRate(); -void worldSetFrameRate(double frameRate); - -double worldGetTimeStep(); - -int worldGetFrameCount(); -double worldGetSeconds(); - -void worldSetInit(Callback init); -void worldSetDraw(Callback draw); -void worldRun(); -void worldStop(); - - -void cameraOn(); -void cameraOff(); -int cameraIsActive(); - -double cameraMouseX(); -double cameraMouseY(); - -double cameraGetX(); -void cameraSetX(double x); - -double cameraGetY(); -void cameraSetY(double y); - -double cameraGetZoom(); -void cameraSetZoom(double zoom); - - -#endif diff --git a/src/SConstruct b/src/SConstruct new file mode 100644 index 0000000..59d2bf2 --- /dev/null +++ b/src/SConstruct @@ -0,0 +1,94 @@ + +env = Environment(tools = ['default', 'textfile']) + +name = 'helianthus' +description = 'Helianthus is a library to create small games' +version = '0.1.0' + + +# user options + +opts = Variables(name + '.conf') +opts.Add(PathVariable('PREFIX', 'Directory to install under', '/usr/local', PathVariable.PathAccept)) +opts.Update(env) +opts.Save(name + '.conf', env) + + +# config + +libs = ['gtk+-3.0', 'glib-2.0', 'cairo', 'SDL2_mixer'] + + +# compute build options + +flags = ' -O0 -g -lm -Wall -fmessage-length=0 ' + + +# files lists + +target = name + +headers = [ + 'common.h', + 'drawing.h', + 'group.h', + 'sprite.h', + 'world.h' ] + +root_headers = [ + name + '.h' ] + +sources = [ + 'animation.c', + 'array.c', + 'collider.c', + 'common.c', + 'drawing.c', + 'group.c', + 'test.c', + 'sound.c', + 'sprite.c', + 'world.c' ] + + +# build + +env.ParseConfig('pkg-config --cflags --libs ' + ' '.join(libs)) + +static_library = env.StaticLibrary( + target = target, + source = sources, + parse_flags = flags ) + +shared_library = env.SharedLibrary( + target = target, + source = sources, + parse_flags = flags, + SHLIBVERSION = version ) + +# install + +idir_prefix = '$PREFIX' +idir_lib = '$PREFIX/lib' +idir_inc = '$PREFIX/include' +env.Export('env idir_prefix idir_lib idir_inc') + +pcdict = { + '@prefix@' : idir_prefix, + '@exec_prefix@' : '$${prefix}', + '@libdir@' : ('$${exec_prefix}/lib' if idir_lib == '$PREFIX/lib' else idir_lib), + '@includedir@' : ('$${prefix}/include' if idir_inc == '$PREFIX/include' else idir_inc) + '/' + name, + '@NAME@' : name, + '@DESC@' : description, + '@VERSION@' : version, + '@DEPS@' : ' '.join(libs), + '@CONFIG_LIBS@' : '-l' + name, + '@CONFIG_CFLAGS@' : '', + } +pcfile = env.Substfile(name + '.pc.in', SUBST_DICT = pcdict) + +env.Install(idir_lib, [static_library, shared_library]) +env.Install(idir_lib + '/pkgconfig', pcfile) +env.Install(idir_inc + '/' + name + '/' + name, headers) +env.Install(idir_inc + '/' + name, root_headers) +env.Alias('install', idir_prefix) diff --git a/src/animation.c b/src/animation.c new file mode 100644 index 0000000..068d7c6 --- /dev/null +++ b/src/animation.c @@ -0,0 +1,76 @@ + +#include +#include + +#include "private.h" + + +static HeliArray cache; + +static cairo_status_t read(void *closure, unsigned char *data, unsigned int length) { + return fread(data, length, 1, (FILE*)closure) + ? CAIRO_STATUS_SUCCESS : CAIRO_STATUS_READ_ERROR; +} + +static cairo_surface_t* loadFrame(const char *path) { + if (!heliStringEndsWithLowcase(path, ".png")) + return NULL; + FILE *f = fopen(path, "rb"); + if (f) { + cairo_surface_t *frame = cairo_image_surface_create_from_png_stream(&read, f); + fclose(f); + if (!frame) + fprintf(stderr, "helianthus: cannot load PNG content form file: %s\n", path); + return frame; + } + fprintf(stderr, "helianthus: cannot open image file: %s\n", path); + return NULL; +} + +static HeliAnimation* load(const char *path) { + HeliAnimation *a = calloc(1, sizeof(*a)); + a->path = heliStringCopy(path); + + GDir *dir = g_dir_open(path, 0, NULL); + if (dir) { + while(TRUE) { + const char* name = g_dir_read_name(dir); + if (!name) break; + char *p = heliStringConcat3(path, "/", name); + cairo_surface_t *frame = loadFrame(p); + free(p); + if (frame) heliArrayInsert(&a->frames, -1, frame, (HeliFreeCallback)&cairo_surface_destroy); + } + } else { + cairo_surface_t *frame = loadFrame(path); + if (frame) { + heliArrayInsert(&a->frames, -1, frame, (HeliFreeCallback)&cairo_surface_destroy); + } else { + fprintf(stderr, "helianthus: cannot load animation by path: %s\n", path); + } + } + + return a; +} + +static void unload(HeliAnimation *a) { + assert(!a->refcount); + free(a->path); + heliArrayDestroy(&a->frames); + free(a); +} + +HeliAnimation *heliCreateAnimation(const char *path) { + HeliPair *item = heliStringmapGet(&cache, path); + if (!item) item = heliStringmapAdd(&cache, path, load(path), (HeliFreeCallback)&unload); + HeliAnimation *a = (HeliAnimation*)item->value; + ++a->refcount; + return a; +} + +void heliAnimationUnref(HeliAnimation *a) + { if (--a->refcount <= 0) heliStringmapRemove(&cache, a->path); } + +void heliAnimationFinish() + { heliArrayDestroy(&cache); } + diff --git a/src/array.c b/src/array.c new file mode 100644 index 0000000..d6b56d2 --- /dev/null +++ b/src/array.c @@ -0,0 +1,133 @@ + +#include "private.h" + + +void heliPairInit(HeliPair *p) { + p->key = p->value = NULL; + p->freeKey = p->freeValue = NULL; +} + +void heliPairDestroy(HeliPair *p) { + if (p->key && p->freeKey) p->freeKey(p->key); + if (p->value && p->freeValue) p->freeKey(p->value); + heliPairInit(p); +} + +void heliArrayInit(HeliArray *a) { + a->items = NULL; + a->count = a->allocated = 0; +} + +void heliArrayDestroy(HeliArray *a) { + heliArrayClear(a); + free(a->items); + heliArrayInit(a); +} + +HeliPair* heliArrayGet(HeliArray *a, int i) + { return i >= 0 && i < a->count ? &a->items[i] : NULL; } + +void* heliArrayGetKey(HeliArray *a, int i) { + HeliPair *item = heliArrayGet(a, i); + return item ? item->key : NULL; +} + +void* heliArrayGetValue(HeliArray *a, int i) { + HeliPair *item = heliArrayGet(a, i); + return item ? item->value : NULL; +} + +HeliPair* heliArrayInsertPair(HeliArray *a, int i, void *k, void *v, HeliFreeCallback fk, HeliFreeCallback fv) { + if (i < 0 || i > a->count) + i = a->count; + if (a->allocated < a->count + 1) { + a->allocated += a->allocated/4 + 32; + a->items = realloc(a->items, a->allocated*sizeof(a->items[0])); + memset(&a->items[a->count], 0, (a->allocated - a->count)*sizeof(a->items[0])); + } + if (i < a->count) { + memmove(&a->items[i+1], &a->items[i], (a->count-i)*sizeof(a->items[0])); + memset(&a->items[i], 0, sizeof(a->items[0])); + } + ++a->count; + + HeliPair *item = &a->items[i]; + item->key = k; + item->value = v; + item->freeKey = fk; + item->freeValue = fv; + return item; +} + +HeliPair* heliArrayInsert(HeliArray *a, int i, void *v, HeliFreeCallback fv) + { return heliArrayInsertPair(a, i, NULL, v, NULL, fv); } + +void heliArrayRemove(HeliArray *a, int i) { + if (i < 0 || i >= a->count) return; + heliPairDestroy(&a->items[i]); + memmove(&a->items[i], &a->items[i+1], (a->count-i-1)*sizeof(a->items[0])); + --a->count; + memset(&a->items[a->count], 0, sizeof(a->items[0])); +} + +void heliArrayClear(HeliArray *a) { + while(a->count > 0) + heliArrayRemove(a, a->count-1); +} + +HeliPair* heliStringmapFind(HeliArray *a, const char *k, int *gtOrEqIndex) { + int i0 = 0; + int i1 = a->count - 1; + int cmp = i0 < a->count ? strcmp((char*)a->items[i0].key, k) : -1; + if (cmp >= 0) { + if (gtOrEqIndex) *gtOrEqIndex = i0; + return cmp ? NULL : &a->items[i0]; + } + if (i1 > i0) cmp = strcmp((char*)a->items[i1].key, k); + if (cmp < 0) { + if (gtOrEqIndex) *gtOrEqIndex = a->count; + return NULL; + } + if (cmp == 0) { + if (gtOrEqIndex) *gtOrEqIndex = i1; + return &a->items[i1]; + } + + while(i0 + 1 < i1) { + int i = (i0 + i1)/2; + cmp = strcmp((char*)a->items[i].key, k); + if (cmp == 0) { + if (gtOrEqIndex) *gtOrEqIndex = i; + return &a->items[i]; + } + if (cmp < 0) i0 = i; else i1 = i; + } + if (gtOrEqIndex) *gtOrEqIndex = i1; + return NULL; +} + +HeliPair* heliStringmapGet(HeliArray *a, const char *k) + { return heliStringmapFind(a, k, NULL); } + +HeliPair* heliStringmapAdd(HeliArray *a, const char *k, void *v, HeliFreeCallback fv) { + int i; + HeliPair *item = heliStringmapFind(a, k, &i); + if (item) { + if (item->value && item->freeValue) item->freeValue(item->value); + item->value = v; + item->freeValue = fv; + } else { + item = heliArrayInsertPair(a, i, heliStringCopy(k), v, &free, fv); + } + return item; +} + +int heliStringmapRemove(HeliArray *a, const char *k) { + int i; + if (heliStringmapFind(a, k, &i)) { + heliArrayRemove(a, i); + return TRUE; + } + return FALSE; +} + diff --git a/src/collider.c b/src/collider.c new file mode 100644 index 0000000..500cc28 --- /dev/null +++ b/src/collider.c @@ -0,0 +1,27 @@ + +#include "private.h" + +int heliCheckCollision(HeliCollider *a, HeliCollider *b, double *normX, double *normY) { + // corner with side + // corner with circle + // circle with circle + #warning "TODO: heliCheckCollision: not implemented yet" + return FALSE; +} + +int heliPointCollision(HeliCollider *c, double x, double y) { + x -= c->x; + y -= c->y; + if (c->type == COLLIDER_CIRCLE) { + return x*x + y*y <= c->radius*c->radius; + } else + if (c->type == COLLIDER_RECTANGLE) { + double a = c->rotation*(PI/180); + double sn = sin(a); + double cn = cos(a); + return fabs(x*cn - y*sn) <= c->width*0.5 + && fabs(x*sn + y*cn) <= c->height*0.5; + } + return FALSE; +} + diff --git a/src/common.c b/src/common.c new file mode 100644 index 0000000..a46a70f --- /dev/null +++ b/src/common.c @@ -0,0 +1,96 @@ + +#include "private.h" + + +cairo_t *heliCairo; + + +static char *colors[] = { + "transparent", "0 0 0 0", + + "black", "0 0 0 1", + "white", "1 1 1 1", + "gray", "0.5 0.5 0.5 1", + + "red", "1 0 0 1", + "green" "0 1 0 1", + "blue" "0 0 1 1", + + "yellow", "1 1 0 1", + "magenta", "1 0 1 1", + "cyan", "0 1 1 1", + + "yellow", "1 1 0 1", + "magenta", "1 0 1 1", + "cyan", "0 1 1 1", + + "brown", "0.5 0.5 0 1", +}; + + +int randomNumber(int min, int max) + { return max <= min ? min : rand()%(max - min + 1) + min; } + +double randomFloat() + { return (double)rand()/(double)RAND_MAX; } + + +char* heliStringCopy(const char *x) { + int len = strlen(x) + 1; + char *cp = malloc(len + 1); + memcpy(cp, x, len); + return cp; +} + +char* heliStringConcat3(const char *a, const char *b, const char *c) { + int la = strlen(a); + int lb = strlen(b); + int lc = strlen(c); + char *s = malloc(la + lb + lc + 1); + memcpy(s, a, la); + memcpy(s + la, b, lb); + memcpy(s + la + lb, c, lc); + s[la + lb + lc] = 0; + return s; +} + +int heliStringEndsWithLowcase(const char *s, const char *tail) { + int ls = strlen(s); + int lt = strlen(tail); + if (lt > ls) return FALSE; + for(int i = 0; i < lt; ++i) + if (tolower(s[ls - i]) != tolower(tail[lt - i])) + return FALSE; + return TRUE; +} + +void heliLowercase(char *x) + { while(*x) { *x = tolower(*x); ++x; } } + + +void heliParseColor(const char *x, double *color) { + color[0] = color[1] = color[2] = 0; + color[3] = 1; + + if (*x == '#') { + ++x; + int hex[8] = { 0, 0, 0, 0, 0, 0, 15, 15 }; + for(int i = 0; *x && i < 8; ++i) { + char c = tolower(*x); + if (c >= '0' && c <= '9') hex[i] = c - '0'; + if (c >= 'a' && c <= 'f') hex[i] = c - 'a' + 10; + } + for(int i = 0; i < 4; ++i) + color[i] = (hex[i*2]*16 + hex[i*2 + 1])/255.0; + } else + if (isalpha(*x)) { + int count = (int)(sizeof(colors)/sizeof(*colors)/2); + for(int i = 0; i < count; ++i) + if (strcmp(x, colors[i*2]) == 0) + { heliParseColor(colors[i*2 + 1], color); return; } + } else { + sscanf(x, "%lf %lf %lf %lf", &color[0], &color[1], &color[2], &color[3]); + } +} + + diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..1a25cb9 --- /dev/null +++ b/src/common.h @@ -0,0 +1,22 @@ +#ifndef HELI_COMMON_H +#define HELI_COMMON_H + + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef PI +#define PI 3.14159265358979323846 +#endif + + +int randomNumber(int min, int max); +double randomFloat(); + + +#endif diff --git a/src/drawing.c b/src/drawing.c new file mode 100644 index 0000000..ba5e348 --- /dev/null +++ b/src/drawing.c @@ -0,0 +1,197 @@ + +#include + +#include "private.h" +#include "drawing.h" + +static double colorBack[4] = {1, 1, 1, 1}; +static double colorFill[4] = {0.5, 0.5, 0.5, 1}; +static double colorStroke[4] = {0, 0, 0, 1}; +static double lineWidth = 1; +static double *path; +static size_t pathSize; +static size_t pathAllocated; + +static HAlign horAlign = HALIGN_LEFT; +static VAlign vertAlign = VALIGN_TOP; +static char *font; +static double fontSize = 24; + + +void background(const char *color) + { heliParseColor(color, colorBack); } +void fill(const char *color) + { heliParseColor(color, colorFill); } +void noFill() + { fill("transparent"); } +void stroke(const char *color) + { heliParseColor(color, colorStroke); } +void noStroke() + { stroke("transparent"); } + +void strokeWeight(double weight) + { lineWidth = weight; } + +char* rgba(double r, double g, double b, double a) { + static char buf[1024]; + snprintf(buf, sizeof(buf) - 1, "%f %f %f %f", r, g, b, a); + return buf; +} + +char* rgb(double r, double g, double b) + { return rgba(r, g, b, 1); } + +void rect(double x, double y, double width, double height) { + resetPath(); + moveTo(x, y); + lineTo(x + width, y); + lineTo(x + width, y + height); + lineTo(x, y + height); + closePath(); +} + +void line(double x1, double y1, double x2, double y2) { + resetPath(); + moveTo(x1, y1); + lineTo(x2, y2); + strokePath(); +} + +void ellipse(double x, double y, double width, double height) { + resetPath(); + arcPath(x, y, width, height, 0, 360); + closePath(); +} + +void point(double x, double y) + { ellipse(x - lineWidth*0.25, y - lineWidth*0.25, lineWidth*0.5, lineWidth*0.5); } + +void arcPath(double x, double y, double w, double h, double start, double stop) { + double step = PI/180; + start *= PI/180; + stop *= PI/180; + if (start < stop) { double s = start; stop = start; start = s; } + for(double a = start; a < stop; a += step) { + double lx = x + cos(a)*w; + double ly = y + sin(a)*h; + lineTo(lx, ly); + } +} + +void arc(double x, double y, double w, double h, double start, double stop) { + resetPath(); + arcPath(x, y, w, h, start, stop); + strokePath(); +} + +void regularPolygon(double x, double y, int sides, double size) { + resetPath(); + size *= 0.5; + moveTo(x + size, y); + for(int i = 1; i < sides; ++i) { + double a = i*PI/sides; + lineTo(x + size*cos(a), y + size*sin(a)); + } + closePath(); +} + + +void resetPath() + { pathSize = 0; } + +static void endPath(int close, int stroke, int fill) { + cairo_t *cr = heliCairo; + if (cr && pathSize >= 8) { + cairo_save(cr); + cairo_move_to(cr, path[0], path[1]); + for(int i = 2; i < pathSize; i += 2) + cairo_line_to(cr, path[i], path[i+1]); + if (close) + cairo_close_path(cr); + if (fill) { + cairo_set_source_rgba(cr, colorFill[0], colorFill[1], colorFill[2], colorFill[3]); + if (stroke) cairo_fill_preserve(cr); else cairo_fill(cr); + } + if (stroke) { + cairo_set_line_width(cr, lineWidth); + cairo_set_source_rgba(cr, colorStroke[0], colorStroke[1], colorStroke[2], colorStroke[3]); + cairo_stroke(cr); + } + cairo_restore(cr); + } + resetPath(); +} + +static void pushPathPoint(double x, double y) { + if (pathAllocated < pathSize + 2) { + pathAllocated += pathAllocated/4 + 32; + path = realloc(path, pathAllocated); + memset(&path[pathSize], 0, (pathAllocated - pathSize)*sizeof(*path)); + } + path[pathSize++] = x; + path[pathSize++] = y; +} + +void closePath() + { endPath(TRUE, TRUE, TRUE); } +void strokePath() + { endPath(FALSE, TRUE, FALSE); } +void lineTo(double x, double y) + { pushPathPoint(x, y); } +void moveTo(double x, double y) + { resetPath(); lineTo(x, y); } + + +void textAlign(HAlign hor, VAlign vert) + { horAlign = hor; vertAlign = vert; } +void textFont(const char *f) + { free(font); font = heliStringCopy(f); } +void textSize(double size) + { fontSize = size; } + +void text(const char *text, double x, double y) { + resetPath(); + + cairo_t *cr = heliCairo; + if (!cr) return; + cairo_save(cr); + + if (font) cairo_select_font_face(cr, font, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); + cairo_set_font_size(cr, fontSize); + + cairo_text_extents_t extents; + cairo_text_extents(cr, text, &extents); + double w = extents.x_bearing + extents.width; + double h = extents.y_bearing + extents.height; + if (horAlign == HALIGN_CENTER) x -= w*0.5; + if (horAlign == HALIGN_RIGHT ) x -= w; + if (vertAlign == VALIGN_CENTER) y -= h*0.5; + if (vertAlign == VALIGN_BOTTOM) y -= h; + + cairo_set_source_rgba(cr, colorStroke[0], colorStroke[1], colorStroke[2], colorStroke[3]); + cairo_move_to(cr, x, y); + cairo_show_text(cr, text); + + cairo_restore(cr); +} + +void heliDrawingClearFrame(cairo_t *cr) { + cairo_save(cr); + cairo_set_source_rgba(cr, colorBack[0], colorBack[1], colorBack[2], 1); + cairo_paint(cr); + cairo_restore(cr); +} + +void heliDrawingPrepareFrame() { + resetPath(); + if (heliCairo) heliDrawingClearFrame(heliCairo); +} + +void heliDrawingFinish() { + resetPath(); + free(path); + path = NULL; + pathAllocated = 0; + free(font); + font = NULL; +} diff --git a/src/drawing.h b/src/drawing.h new file mode 100644 index 0000000..3fa6f4a --- /dev/null +++ b/src/drawing.h @@ -0,0 +1,48 @@ +#ifndef HELI_DRAWING_H +#define HELI_DRAWING_H + + +#include "common.h" + +typedef enum _HAlign { + HALIGN_LEFT, + HALIGN_CENTER, + HALIGN_RIGHT +} HAlign; + +typedef enum _VAlign { + VALIGN_TOP, + VALIGN_CENTER, + VALIGN_BOTTOM +} VAlign; + + +void background(const char *color); +void fill(const char *color); +void noFill(); +void stroke(const char *color); +void noStroke(); +void strokeWeight(double weight); +char* rgb(double r, double g, double b); +char* rgba(double r, double g, double b, double a); +void rect(double x, double y, double width, double height); +void ellipse(double x, double y, double width, double height); +void text(const char *text, double x, double y); +void textAlign(HAlign hor, VAlign vert); +void textFont(const char *font); +void textSize(double size); +void arc(double x, double y, double w, double h, double start, double stop); +void arcPath(double x, double y, double w, double h, double start, double stop); +void line(double x1, double y1, double x2, double y2); +void point(double x, double y); +void regularPolygon(double x, double y, int sides, double size); + +void moveTo(double x, double y); +void lineTo(double x, double y); +void resetPath(); +void closePath(); +void strokePath(); + + +#endif + diff --git a/src/group.c b/src/group.c new file mode 100644 index 0000000..400bffd --- /dev/null +++ b/src/group.c @@ -0,0 +1,209 @@ + +#include "private.h" +#include "group.h" +#include "world.h" + +struct _Group { + HeliArray sprites; +}; + + +Group createGroup() { + Group g = calloc(1, sizeof(*g)); + return g; +} + +Group createEdgesGroupEx(double x1, double y1, double x2, double y2, double borderWidth) { + if (x2 < x1) { double x = x2; x2 = x1; x1 = x; } + if (y2 < y1) { double y = y2; y2 = y1; y1 = y; } + if (borderWidth < 0.1) borderWidth = 0.1; + + double b = borderWidth; + double w = x2 - x1 + b*2; + double h = y2 - y1 + b*2; + x1 -= b; + y1 -= b; + + Group g = createGroup(); + groupAdd(g, createSpriteEx(x1, y1, w, b)); + groupAdd(g, createSpriteEx(x1, y2, w, b)); + groupAdd(g, createSpriteEx(x1, y1, b, h)); + groupAdd(g, createSpriteEx(x2, y1, b, h)); + return g; +} + +Group createEdgesGroup() + { return createEdgesGroupEx(0, 0, worldGetWidth(), worldGetHeight(), 10); } + + +void groupDestroy(Group group) { + heliArrayDestroy(&group->sprites); + free(group); +} + +void groupDestroyEx(Group group, int destroySprites) { + groupClearEx(group, destroySprites); + groupDestroy(group); +} + +void groupAdd(Group group, Sprite sprite) { + if (groupContains(group, sprite)) return; + heliArrayInsert(&group->sprites, -1, sprite, NULL); + heliArrayInsert(heliSpriteGetGroups(sprite), -1, sprite, NULL); +} + +void groupRemove(Group group, Sprite sprite) { + for(int i = group->sprites.count-1; i >= 0; --i) + if (group->sprites.items[i].value == sprite) + heliArrayRemove(&group->sprites, i); + HeliArray *groups = heliSpriteGetGroups(sprite); + for(int i = groups->count-1; i >= 0; --i) + if (groups->items[i].value == group) + heliArrayRemove(groups, i); +} + +void groupClearEx(Group group, int destroySprites) { + while(groupGetCount(group) > 0) { + Sprite s = groupGet(group, 0); + groupRemove(group, s); + if (destroySprites) spriteDestroy(s); + } +} + +void groupClear(Group group) + { groupClearEx(group, FALSE); } +void groupDestroyEach(Group group) + { groupClearEx(group, TRUE); } + +int groupContains(Group group, Sprite sprite) { + for(int i = group->sprites.count-1; i >= 0; --i) + if (group->sprites.items[i].value == sprite) + return TRUE; + return FALSE; +} + +int groupGetCount(Group group) + { return group->sprites.count; } +Sprite groupGet(Group group, int i) + { return (Sprite)heliArrayGetValue(&group->sprites, i); } + +int groupOverlap(Group group, Sprite sprite) + { return groupCollideEx(group, sprite, TRUE, TRUE, 0); } +int groupCollide(Group group, Sprite sprite, double bounciness) + { return groupCollideEx(group, sprite, FALSE, FALSE, bounciness); } +int groupBounceOff(Group group, Sprite sprite, double bounciness) + { return groupCollideEx(group, sprite, FALSE, TRUE, bounciness); } +int groupPush(Group group, Sprite sprite, double bounciness) + { return groupCollideEx(group, sprite, TRUE, FALSE, bounciness); } +int groupCollideEx(Group group, Sprite sprite, int keepVelocityGroup, int keepVelocitySprite, double bounciness) { + int result = FALSE; + for(int i = 0; i < groupGetCount(group); ++i) + if (spriteCollideEx(groupGet(group, i), sprite, keepVelocityGroup, keepVelocitySprite, bounciness)) + result = TRUE; + return result; +} + +int groupOverlapGroup(Group a, Group b) + { return groupCollideGroupEx(a, b, TRUE, TRUE, 0); } +int groupCollideGroup(Group a, Group b, double bounciness) + { return groupCollideGroupEx(a, b, FALSE, FALSE, bounciness); } +int groupBounceOffGroup(Group group, Group other, double bounciness) + { return groupCollideGroupEx(group, other, FALSE, TRUE, bounciness); } +int groupPushGroup(Group group, Group other, double bounciness) + { return groupCollideGroupEx(group, other, TRUE, FALSE, bounciness); } +int groupCollideGroupEx(Group a, Group b, int keepVelocityA, int keepVelocityB, double bounciness) { + int result = FALSE; + for(int i = 0; i < groupGetCount(b); ++i) + if (groupCollideEx(a, groupGet(b, i), keepVelocityA, keepVelocityB, bounciness)) + result = TRUE; + return result; +} + +double groupGetMinDepth(Group group) { + if (groupGetCount(group) <= 0) return 0; + double md = spriteGetDepth(groupGet(group, 0)); + for(int i = 1; i < groupGetCount(group); ++i) { + double d = spriteGetDepth(groupGet(group, i)); + if (d < md) md = d; + } + return md; +} + +double groupGetMaxDepth(Group group) { + if (groupGetCount(group) <= 0) return 0; + double md = spriteGetDepth(groupGet(group, 0)); + for(int i = 1; i < groupGetCount(group); ++i) { + double d = spriteGetDepth(groupGet(group, i)); + if (d > md) md = d; + } + return md; +} + + +static void foreachInt(Group group, int value, HeliSpriteEashInt func) + { for(int i = 0; i < groupGetCount(group); ++i) func(groupGet(group, i), value); } +static void foreachDouble(Group group, double value, HeliSpriteEashDouble func) + { for(int i = 0; i < groupGetCount(group); ++i) func(groupGet(group, i), value); } +static void foreachString(Group group, const char *value, HeliSpriteEashString func) + { for(int i = 0; i < groupGetCount(group); ++i) func(groupGet(group, i), value); } + + +void groupDestroyTimerEach(Group group, double lifetime) + { foreachDouble(group, lifetime, &spriteDestroyTimer); } + +void groupSetVisibleEach(Group group, int visible) + { foreachInt(group, visible, &spriteSetVisible); } +void groupSetWidthEach(Group group, double width) + { foreachDouble(group, width, &spriteSetWidth); } +void groupSetHeightEach(Group group, double height) + { foreachDouble(group, height, &spriteSetHeight); } +void groupSetDepthEach(Group group, double depth) + { foreachDouble(group, depth, &spriteSetDepth); } +void groupSetVelocityXEach(Group group, double x) + { foreachDouble(group, x, &spriteSetVelocityX); } +void groupSetVelocityYEach(Group group, double y) + { foreachDouble(group, y, &spriteSetVelocityY); } +void groupSetRotateToDirectionEach(Group group, int rotateToDirection) + { foreachInt(group, rotateToDirection, &spriteSetRotateToDirection); } +void groupSetRotationEach(Group group, double rotation) + { foreachDouble(group, rotation, &spriteSetRotation); } +void groupSetRotationSpeedEach(Group group, double rotationSpeed) + { foreachDouble(group, rotationSpeed, &spriteSetRotationSpeed); } +void groupSetScaleEach(Group group, double scale) + { foreachDouble(group, scale, &spriteSetScale); } +void groupSetMirrorXEach(Group group, int mirrorX) + { foreachInt(group, mirrorX, &spriteSetMirrorX); } +void groupSetMirrorYEach(Group group, int mirrorY) + { foreachInt(group, mirrorY, &spriteSetMirrorY); } +void groupSetAnimationEach(Group group, const char *path) + { foreachString(group, path, &spriteSetAnimation); } +void groupSetShapeColorEach(Group group, const char *color) + { foreachString(group, color, &spriteSetShapeColor); } +void groupSetTintColorEach(Group group, const char *color) + { foreachString(group, color, &spriteSetTintColor); } + +void groupPointToEach(Group group, double x, double y) { + for(int i = 0; i < groupGetCount(group); ++i) + spritePointTo(groupGet(group, i), x, y); +} + +void groupSetSpeedAndDirectionEach(Group group, double speed, double angle) { + for(int i = 0; i < groupGetCount(group); ++i) + spriteSetSpeedAndDirection(groupGet(group, i), speed, angle); +} + +void groupSetVelocityEach(Group group, double x, double y) { + for(int i = 0; i < groupGetCount(group); ++i) + spriteSetVelocityXY(groupGet(group, i), x, y); +} + +void groupSetColliderEachEx(Group group, Collider type, double xOffset, double yOffset, + double widthOrRadius, double height, double rotationOffset) +{ + for(int i = 0; i < groupGetCount(group); ++i) + spriteSetColliderEx(groupGet(group, i), type, xOffset, yOffset, + widthOrRadius, height, rotationOffset); +} + +void groupSetColliderEach(Group group, Collider type, double xOffset, double yOffset) + { groupSetColliderEachEx(group, type, xOffset, yOffset, -1, -1, 0); } diff --git a/src/group.h b/src/group.h new file mode 100644 index 0000000..241577c --- /dev/null +++ b/src/group.h @@ -0,0 +1,68 @@ +#ifndef HELI_GROUP_H +#define HELI_GROUP_H + + +#include "sprite.h" + + +typedef struct _Group *Group; + + +Group createGroup(); +Group createEdgesGroup(); +Group createEdgesGroupEx(double x1, double y1, double x2, double y2, double borderWidth); + +void groupDestroy(Group group); +void groupDestroyEx(Group group, int destroySprites); + +void groupAdd(Group group, Sprite sprite); +void groupRemove(Group group, Sprite sprite); +void groupClear(Group group); +void groupClearEx(Group group, int destroySprites); +int groupContains(Group group, Sprite sprite); +int groupGetCount(Group group); +Sprite groupGet(Group group, int i); + +int groupOverlap(Group group, Sprite sprite); +int groupCollide(Group group, Sprite sprite, double bounciness); +int groupBounceOff(Group group, Sprite sprite, double bounciness); +int groupPush(Group group, Sprite sprite, double bounciness); +int groupCollideEx(Group group, Sprite sprite, int keepVelocityGroup, int keepVelocitySprite, double bounciness); + +int groupOverlapGroup(Group a, Group b); +int groupCollideGroup(Group a, Group b, double bounciness); +int groupBounceOffGroup(Group group, Group other, double bounciness); +int groupPushGroup(Group group, Group other, double bounciness); +int groupCollideGroupEx(Group a, Group b, int keepVelocityA, int keepVelocityB, double bounciness); + +double groupGetMinDepth(Group group); +double groupGetMaxDepth(Group group); + +void groupDestroyEach(Group group); +void groupDestroyTimerEach(Group group, double lifetime); + +void groupSetVisibleEach(Group group, int visible); +void groupSetWidthEach(Group group, double width); +void groupSetHeightEach(Group group, double height); +void groupSetDepthEach(Group group, double depth); +void groupSetVelocityXEach(Group group, double x); +void groupSetVelocityYEach(Group group, double y); +void groupSetVelocityEach(Group group, double x, double y); +void groupSetLifetimeEach(Group group, double lifetime); +void groupSetRotateToDirectionEach(Group group, int rotateToDirection); +void groupSetRotationEach(Group group, double rotation); +void groupSetRotationSpeedEach(Group group, double rotationSpeed); +void groupSetScaleEach(Group group, double scale); +void groupSetMirrorXEach(Group group, int mirrorX); +void groupSetMirrorYEach(Group group, int mirrorY); +void groupPointToEach(Group group, double x, double y); +void groupSetSpeedAndDirectionEach(Group group, double speed, double angle); +void groupSetAnimationEach(Group group, const char *path); +void groupSetShapeColorEach(Group group, const char *color); +void groupSetTintColorEach(Group group, const char *color); +void groupSetColliderEach(Group group, Collider type, double xOffset, double yOffset); +void groupSetColliderEachEx(Group group, Collider type, double xOffset, double yOffset, + double widthOrRadius, double height, double rotationOffset); + + +#endif diff --git a/src/helianthus.h b/src/helianthus.h new file mode 100644 index 0000000..9990ca7 --- /dev/null +++ b/src/helianthus.h @@ -0,0 +1,12 @@ +#ifndef HELI_HELIANTUS_H +#define HELI_HELIANTUS_H + + +#include "helianthus/common.h" +#include "helianthus/world.h" +#include "helianthus/sprite.h" +#include "helianthus/group.h" +#include "helianthus/drawing.h" + + +#endif diff --git a/src/helianthus.pc.in b/src/helianthus.pc.in new file mode 100644 index 0000000..f4a75d3 --- /dev/null +++ b/src/helianthus.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: @NAME@ +Description: @DESC@ +Requires: @DEPS@ +Version: @VERSION@ +Libs: -L${libdir} @CONFIG_LIBS@ +Cflags: @CONFIG_CFLAGS@ -I${includedir} + diff --git a/src/private.h b/src/private.h new file mode 100644 index 0000000..34b7d47 --- /dev/null +++ b/src/private.h @@ -0,0 +1,120 @@ + +#include +#include +#include +#include +#include +#include + +#include + +#include "common.h" +#include "sprite.h" + + +// globals + +extern cairo_t *heliCairo; + + +// string + +char* heliStringCopy(const char *x); +char* heliStringConcat3(const char *a, const char *b, const char *c); +int heliStringEndsWithLowcase(const char *s, const char *tail); +void heliLowercase(char *x); +void heliParseColor(const char *x, double *color); + + +// pointer array + +typedef void (*HeliFreeCallback)(void*); + +typedef struct _HeliPair { + void *key; + void *value; + HeliFreeCallback freeKey; + HeliFreeCallback freeValue; +} HeliPair; + +typedef struct _HeliArray { + HeliPair *items; + int count; + int allocated; +} HeliArray; + +void heliPairInit(HeliPair *p); +void heliPairDestroy(HeliPair *p); + +void heliArrayInit(HeliArray *a); +void heliArrayClear(HeliArray *a); +void heliArrayDestroy(HeliArray *a); +HeliPair* heliArrayGet(HeliArray *a, int i); +void* heliArrayGetKey(HeliArray *a, int i); +void* heliArrayGetValue(HeliArray *a, int i); +HeliPair* heliArrayInsert(HeliArray *a, int i, void *v, HeliFreeCallback fv); +HeliPair* heliArrayInsertPair(HeliArray *a, int i, void *k, void *v, HeliFreeCallback fk, HeliFreeCallback fv); +void heliArrayRemove(HeliArray *a, int i); + +HeliPair* heliStringmapFind(HeliArray *a, const char *k, int *gtOrEqIndex); +HeliPair* heliStringmapGet(HeliArray *a, const char *k); +HeliPair* heliStringmapAdd(HeliArray *a, const char *k, void *v, HeliFreeCallback fv); +int heliStringmapRemove(HeliArray *a, const char *k); + + +// animation + +typedef struct _HeliAnimation { + char *path; + HeliArray frames; + int refcount; +} HeliAnimation; + +HeliAnimation *heliCreateAnimation(const char *path); +void heliAnimationUnref(HeliAnimation *a); +void heliAnimationFinish(); + + +// collider + +typedef struct _HeliCollider { + Collider type; + double x; + double y; + double radius; + double width; + double height; + double rotation; +} HeliCollider; + +int heliCheckCollision(HeliCollider *a, HeliCollider *b, double *normX, double *normY); +int heliPointCollision(HeliCollider *c, double x, double y); + + +// sprite + +typedef void (*HeliSpriteEashInt)(Sprite, int); +typedef void (*HeliSpriteEashDouble)(Sprite, double); +typedef void (*HeliSpriteEashString)(Sprite, const char*); +HeliArray* heliSpriteGetGroups(Sprite sprite); +void heliSpriteUpdate(double dt); +void heliSpriteFinish(); + + +// drawing + +void heliDrawingClearFrame(cairo_t *cr); +void heliDrawingPrepareFrame(); +void heliDrawingFinish(); + + +// sound + +void heliSoundUpdate(); +void heliSoundFinish(); + + +// test + +void heliDoTests(); + diff --git a/src/sound.c b/src/sound.c new file mode 100644 index 0000000..aa36d92 --- /dev/null +++ b/src/sound.c @@ -0,0 +1,163 @@ + +#include + +#include +#include + +#include "private.h" +#include "world.h" + + +#define HELI_SOUND_CHANNELS 64 + + +typedef struct _HeliSoundInstance { + char *path; + Mix_Chunk *chunk; + int refcount; +} HeliSoundInstance; + +typedef struct _HeliSoundTrashItem { + Mix_Chunk *chunk; + gint64 time; +} HeliSoundTrashItem; + +struct _Sound { + HeliSoundInstance *instance; +}; + + +static int initialized; +static int ready; + +static HeliArray cache; +static Sound channels[HELI_SOUND_CHANNELS]; + +static HeliSoundTrashItem *trash; +static size_t trashSize; + + +static void trashAdd(Mix_Chunk *chunk) { + if (!chunk) return; + + gint64 time = g_get_monotonic_time() + 1000000; + for(int i = 0; i < trashSize; ++i) + if (!trash[i].chunk) + { trash[i].chunk = chunk; trash[i].time = time; return; } + + size_t prevSize = trashSize; + trashSize += trashSize/4 + 32; + trash = realloc(trash, trashSize); + memset(&trash[prevSize], 0, (trashSize - prevSize)*sizeof(*trash)); + + trash[prevSize].chunk = chunk; + trash[prevSize].time = time; +} + +static void trashProcess() { + gint64 time = g_get_monotonic_time(); + for(int i = 0; i < trashSize; ++i) + if (trash[i].chunk && trash[i].time <= time) + { Mix_FreeChunk(trash[i].chunk); trash[i].chunk = NULL; } +} + +static void trashDestroy() { + for(int i = 0; i < trashSize; ++i) + if (trash[i].chunk) Mix_FreeChunk(trash[i].chunk); + trashSize = 0; + free(trash); + trash = NULL; +} + + +static void init() { + if (initialized) return; + if (SDL_Init(SDL_INIT_AUDIO) == 0) { + int loaded = Mix_Init(MIX_INIT_OGG | MIX_INIT_FLAC | MIX_INIT_MP3); + if (!(loaded & MIX_INIT_OGG )) fprintf(stderr, "helianthus: cannot initialize OGG support\n"); + if (!(loaded & MIX_INIT_FLAC)) fprintf(stderr, "helianthus: cannot initialize FLAC support\n"); + if (!(loaded & MIX_INIT_MP3 )) fprintf(stderr, "helianthus: cannot initialize MP3 support\n"); + ready = Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 1024) == 0; + if (!ready) fprintf(stderr, "helianthus: cannot open audio device\n"); + } + initialized = TRUE; +} + +static void deinit() { + if (!initialized) return; + heliArrayDestroy(&cache); + if (ready) Mix_CloseAudio(); + trashDestroy(); + Mix_Quit(); + SDL_Quit(); + for(int i = 0; i < HELI_SOUND_CHANNELS; ++i) channels[i] = NULL; + ready = FALSE; + initialized = FALSE; +} + + +static HeliSoundInstance* load(const char *path) { + HeliSoundInstance *s = calloc(1, sizeof(*s)); + s->path = heliStringCopy(path); + if (ready) { + s->chunk = Mix_LoadWAV(path); + if (!s->chunk) + fprintf(stderr, "helianthus: cannot load sound file: %s\n", path); + } + return s; +} + +static void unload(HeliSoundInstance *s) { + assert(!s->refcount); + free(s->path); + trashAdd(s->chunk); + free(s); +} + + +Sound createSound(const char *path) { + init(); + HeliPair *item = heliStringmapGet(&cache, path); + if (!item) item = heliStringmapAdd(&cache, path, load(path), (HeliFreeCallback)&unload); + HeliSoundInstance *s = (HeliSoundInstance*)item->value; + ++s->refcount; + + Sound sound = calloc(1, sizeof(*sound)); + sound->instance = s; + return sound; +} + +void soundDestroy(Sound sound) { + soundStop(sound); + if (--sound->instance->refcount <= 0) + heliStringmapRemove(&cache, sound->instance->path); +} + + +void soundPlay(Sound sound, int loop) { + if (!ready) return; + if (sound->instance->chunk) { + int channel = Mix_PlayChannel(-1, sound->instance->chunk, loop ? -1 : 0); + assert(channel < HELI_SOUND_CHANNELS); + if (channel >= 0 && channel < HELI_SOUND_CHANNELS) + channels[channel] = sound; + } +} + +void soundStop(Sound sound) { + for(int i = 0; i < HELI_SOUND_CHANNELS; ++i) { + if (channels[i] == sound) { + Mix_Pause(i); + Mix_HaltChannel(i); + channels[i] = NULL; + } + } +} + + +void heliSoundUpdate() + { trashProcess(); } + +void heliSoundFinish() + { deinit(); } + diff --git a/src/sprite.c b/src/sprite.c new file mode 100644 index 0000000..eb14663 --- /dev/null +++ b/src/sprite.c @@ -0,0 +1,441 @@ + +#include + +#include "private.h" + +#include "sprite.h" +#include "world.h" +#include "group.h" + +struct _Sprite { + double x; + double y; + double rotation; + double width; + double height; + double scale; + + double vx; + double vy; + double rotationSpeed; + + HeliCollider collider; + double bounciness; + + int rotateToDirection; + int mirrorX; + int mirrorY; + double depth; + double lifeTime; + int visible; + int debug; + + double shapeColor[4]; + double tintColor[4]; + + int playing; + int frame; + + HeliArray groups; + + HeliAnimation *animation; +}; + +static HeliArray sprites; +static HeliArray spritesSorted; + + +static void prepareCollider(Sprite sprite, HeliCollider *collider) { + collider->type = sprite->collider.type; + collider->x = sprite->collider.x + sprite->x; + collider->y = sprite->collider.y + sprite->y; + collider->width = sprite->collider.width >= 0 ? sprite->collider.width : sprite->width; + collider->height = sprite->collider.height >= 0 ? sprite->collider.height : sprite->height; + collider->radius = sprite->collider.radius >= 0 ? sprite->collider.radius : collider->width; + collider->rotation = sprite->collider.rotation + sprite->rotation; +} + + + +Sprite createSpriteEx(double x, double y, double width, double height) { + Sprite s = calloc(1, sizeof(*s)); + s->x = x; + s->y = y; + s->width = width; + s->height = height; + + s->scale = 1; + s->collider.type = COLLIDER_RECTANGLE; + s->collider.width = s->collider.height = s->collider.radius = -1; + + s->mirrorX = 1; + s->mirrorY = 1; + s->lifeTime = -1; + s->visible = TRUE; + + s->shapeColor[0] = s->shapeColor[1] = s->shapeColor[2] = 0.5; + s->shapeColor[3] = 1; + s->tintColor[0] = s->tintColor[1] = s->tintColor[2] = s->tintColor[3] = 1; + + heliArrayInsert(&sprites, -1, s, NULL); + heliArrayInsert(&spritesSorted, -1, s, NULL); + + return s; +} + +Sprite createSprite(double x, double y) + { return createSpriteEx(x, y, 100, 100); } + +void spriteDestroy(Sprite sprite) { + while(sprite->groups.count > 0) + groupRemove((Group)sprite->groups.items[0].value, sprite); + for(int i = 0; i < sprites.count; ++i) + if (sprites.items[i].value == sprite) + { heliArrayRemove(&sprites, i); break; } + for(int i = 0; i < spritesSorted.count; ++i) + if (spritesSorted.items[i].value == sprite) + { heliArrayRemove(&spritesSorted, i); break; } + if (sprite->animation) + heliAnimationUnref(sprite->animation); + free(sprite); +} + +void spriteDestroyTimer(Sprite sprite, double lifeTime) { + if (lifeTime <= 0) { spriteDestroy(sprite); return; } + sprite->lifeTime = lifeTime; +} + +double spriteGetX(Sprite sprite) { return sprite->x; } +void spriteSetX(Sprite sprite, double x) { sprite->x = x; } + +double spriteGetY(Sprite sprite) { return sprite->y; } +void spriteSetY(Sprite sprite, double y) { sprite->y = y; } + +double spriteGetVelocityX(Sprite sprite) { return sprite->vx; } +void spriteSetVelocityX(Sprite sprite, double x) + { spriteSetVelocityXY(sprite, x, sprite->vy); } + +double spriteGetVelocityY(Sprite sprite) { return sprite->vy; } +void spriteSetVelocityY(Sprite sprite, double y) + { spriteSetVelocityXY(sprite, sprite->vx, y); } + +double spriteGetScale(Sprite sprite) { return sprite->scale; } +void spriteSetScale(Sprite sprite, double scale) { sprite->scale = scale; } + +double spriteGetWidth(Sprite sprite) { return sprite->width; } +void spriteSetWidth(Sprite sprite, double width) + { sprite->width = width > 0 ? width : 0; } + +double spriteGetHeight(Sprite sprite) { return sprite->height; } +void spriteSetHeight(Sprite sprite, double height) + { sprite->height = height > 0 ? height : 0; } + +int spriteGetRotateToDirection(Sprite sprite) { return sprite->rotateToDirection; } +void spriteSetRotateToDirection(Sprite sprite, int rotateToDirection) { + if (rotateToDirection) { + sprite->rotateToDirection = TRUE; + sprite->rotation = spriteGetDirection(sprite); + } else { + sprite->rotateToDirection = FALSE; + } +} + +double spriteGetRotation(Sprite sprite) { return sprite->rotation; } +void spriteSetRotation(Sprite sprite, double rotation) + { if (!sprite->rotateToDirection) sprite->rotation = rotation; } + +double spriteGetRotationSpeed(Sprite sprite) { return sprite->rotationSpeed; } +void spriteSetRotationSpeed(Sprite sprite, double rotationSpeed) + { sprite->rotationSpeed = rotationSpeed; } + +int spriteGetMirrorX(Sprite sprite) { return sprite->mirrorX; } +void spriteSetMirrorX(Sprite sprite, int mirrorX) + { sprite->mirrorX = mirrorX < 0 ? -1 : 1; } + +int spriteGetMirrorY(Sprite sprite) { return sprite->mirrorY; } +void spriteSetMirrorY(Sprite sprite, int mirrorY) + { sprite->mirrorY = mirrorY < 0 ? -1 : 1; } + +double spriteGetDepth(Sprite sprite) { return sprite->depth; } +void spriteSetDepth(Sprite sprite, double depth) + { sprite->depth = depth; } + +int spriteGetVisible(Sprite sprite) { return sprite->visible; } +void spriteSetVisible(Sprite sprite, int visible) + { sprite->visible = visible != FALSE; } + +int spriteGetDebug(Sprite sprite) { return sprite->debug; } +void spriteSetDebug(Sprite sprite, int debug) + { sprite->debug = debug != FALSE; } + +int spriteOverlap(Sprite a, Sprite b) + { return spriteCollideEx(a, b, TRUE, TRUE, 0); } +int spriteCollide(Sprite a, Sprite b, double bounciness) + { return spriteCollideEx(a, b, FALSE, FALSE, bounciness); } +int spriteBounceOff(Sprite sprite, Sprite other, double bounciness) + { return spriteCollideEx(sprite, other, FALSE, TRUE, bounciness); } +int spritePush(Sprite sprite, Sprite other, double bounciness) + { return spriteCollideEx(sprite, other, TRUE, FALSE, bounciness); } + +int spriteCollideEx(Sprite a, Sprite b, int keepVelocityA, int keepVelocityB, double bounciness) { + #warning "TODO: spriteCollision: not implemented yet" + return FALSE; +} + + +double spriteGetBounciness(Sprite sprite) { return sprite->bounciness; } +void spriteSetBounciness(Sprite sprite, double bounciness) + { sprite->bounciness = bounciness > 0 ? bounciness : 0; } + +void spriteSetColliderEx(Sprite sprite, Collider type, double xOffset, double yOffset, + double widthOrRadius, double height, double rotationOffset) +{ + sprite->collider.type = type; + sprite->collider.x = xOffset; + sprite->collider.y = yOffset; + sprite->collider.width = sprite->collider.radius = widthOrRadius; + sprite->collider.height = height; + sprite->collider.rotation = rotationOffset; +} + +void spriteSetCollider(Sprite sprite, Collider type, double xOffset, double yOffset) + { spriteSetColliderEx(sprite, type, xOffset, yOffset, -1, -1, 0); } + +void spriteSetAnimation(Sprite sprite, const char *path) { + HeliAnimation *prev = sprite->animation; + sprite->animation = heliCreateAnimation(path); + if (prev) heliAnimationUnref(prev); + spriteSetFrame(sprite, sprite->frame); +} + +void spritePlay(Sprite sprite) { sprite->playing = TRUE; } +void spritePause(Sprite sprite) { sprite->playing = FALSE; } +void spriteNextFrame(Sprite sprite) { spriteSetFrame(sprite, sprite->frame + 1); } + +void spriteSetFrame(Sprite sprite, int frame) { + sprite->frame = frame >= 0 && sprite->animation && sprite->animation->frames.count > 0 + ? frame % sprite->animation->frames.count : 0; +} + +void spriteSetShapeColor(Sprite sprite, const char *color) + { heliParseColor(color, sprite->shapeColor); } +void spriteSetTintColor(Sprite sprite, const char *color) + { heliParseColor(color, sprite->tintColor); } + +void spriteSetVelocityXY(Sprite sprite, double x, double y) { + sprite->vx = x; + sprite->vy = y; + if (sprite->rotateToDirection) + sprite->rotation = spriteGetDirection(sprite); +} + +void spriteSetSpeedAndDirection(Sprite sprite, double speed, double angle) { + double a = angle*(PI/180); + spriteSetVelocityXY(sprite, cos(a)*speed, sin(a)*speed); +} + +double spriteGetSpeed(Sprite sprite) + { return sqrt(sprite->vx*sprite->vx + sprite->vy*sprite->vy); } +double spriteGetDirection(Sprite sprite) { + return fabs(sprite->vx) > 1e-6 || fabs(sprite->vy) > 1e-6 + ? atan2(sprite->vy, sprite->vx)*(180/PI) : 0; +} +void spritePointTo(Sprite sprite, double x, double y) + { spriteSetRotation( sprite, atan2(y - sprite->y, x - sprite->x)*(180/PI) ); } + +double spriteGetScaledWidth(Sprite sprite) + { return sprite->width * sprite->scale; } +double spriteGetScaledHeight(Sprite sprite) + { return sprite->height * sprite->scale; } + + + +int worldGetSpriteCount() + { return sprites.count; } + +Sprite worldGetSprite(int i) + { return (Sprite)heliArrayGetValue(&sprites, i); } + + +static void drawSpriteDebug(cairo_t *cr, Sprite s) { + cairo_save(cr); + cairo_set_line_width(cr, 0.5); + 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); + + double hw = s->width*0.5; + double hh = s->height*0.5; + + cairo_set_source_rgba(cr, 0, 0, 0, 0.75); + 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_stroke(cr); + + cairo_move_to(cr, -hw*0.25, 0); + cairo_line_to(cr, hw*0.25, 0); + cairo_move_to(cr, 0, -hh*0.25); + cairo_line_to(cr, 0, hh*0.25); + cairo_stroke(cr); + + char buf[1024]; + snprintf(buf, sizeof(buf)-1, "%lf", s->depth); + + double s1 = hw*0.25; + double s2 = hh*0.5; + cairo_set_font_size(cr, s1 < s2 ? s1 : s2); + + cairo_move_to(cr, -hw*0.9, -hh*0.5); + 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); + + double hw = s->width*0.5; + double hh = s->height*0.5; + + 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) > 1e-5 + || fabs(s->tintColor[1] - 1) > 1e-5 + || fabs(s->tintColor[2] - 1) > 1e-5; + int alpha = fabs(a) > 1e-5; + int visible = fabs(a) > 1e-5; + + if (visible) { + if (tint) cairo_push_group(cr); + + 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 > 1e-6 && ihh > 1e-6) { + 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); + } + } 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); + } + + 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); + } + } + + cairo_restore(cr); +} + +void drawSprites() { + // sort + int found = TRUE; + while(found) { + found = FALSE; + // forward + for(int i = 1; i < spritesSorted.count; ++i) { + if ( ((Sprite)spritesSorted.items[i-1].value)->depth + < ((Sprite)spritesSorted.items[i ].value)->depth ) + { + void *x = spritesSorted.items[i].value; + spritesSorted.items[i].value = spritesSorted.items[i-1].value; + spritesSorted.items[i-1].value = x; + found = TRUE; + } + } + if (!found) break; + // backward + found = FALSE; + for(int i = spritesSorted.count - 1; i > 0; --i) { + if ( ((Sprite)spritesSorted.items[i-1].value)->depth + < ((Sprite)spritesSorted.items[i ].value)->depth ) + { + void *x = spritesSorted.items[i].value; + spritesSorted.items[i].value = spritesSorted.items[i-1].value; + spritesSorted.items[i-1].value = x; + found = TRUE; + } + } + } + + 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); + } + } +} + +int mouseIsOver(Sprite sprite) { + HeliCollider collider; + prepareCollider(sprite, &collider); + return heliPointCollision(&collider, mouseX(), mouseY()); +} + +void heliSpriteUpdate(double dt) { + // auto-remove + for(int i = sprites.count - 1; i > 0; --i) { + Sprite s = (Sprite)sprites.items[i].value; + if (s->lifeTime >= 0) { + s->lifeTime -= dt; + if (s->lifeTime <= 1e-5) + spriteDestroy(s); + } + } + + // update + for(int i = 0; i < sprites.count; ++i) { + Sprite s = (Sprite)sprites.items[i].value; + s->x += s->vx*dt; + s->y += s->vy*dt; + if (!s->rotateToDirection) + s->rotation += s->rotationSpeed*dt; + if (s->playing) spriteNextFrame(s); + } +} + +HeliArray* heliSpriteGetGroups(Sprite sprite) + { return &sprite->groups; } + +void heliSpriteFinish() { + while(worldGetSpriteCount()) + { spriteDestroy(worldGetSprite(0)); } + heliArrayDestroy(&sprites); + heliArrayDestroy(&spritesSorted); +} + + diff --git a/src/sprite.h b/src/sprite.h new file mode 100644 index 0000000..aa5f28c --- /dev/null +++ b/src/sprite.h @@ -0,0 +1,97 @@ +#ifndef HELI_SPRITE_H +#define HELI_SPRITE_H + + +#include "common.h" + +typedef struct _Sprite *Sprite; + + +typedef enum _Collider { + COLLIDER_RECTANGLE, + COLLIDER_CIRCLE +} Collider; + +Sprite createSprite(double x, double y); +Sprite createSpriteEx(double x, double y, double width, double height); + +void spriteDestroy(Sprite sprite); +void spriteDestroyTimer(Sprite sprite, double lifetime); + +double spriteGetX(Sprite sprite); +void spriteSetX(Sprite sprite, double x); + +double spriteGetY(Sprite sprite); +void spriteSetY(Sprite sprite, double y); + +double spriteGetVelocityX(Sprite sprite); +void spriteSetVelocityX(Sprite sprite, double x); + +double spriteGetVelocityY(Sprite sprite); +void spriteSetVelocityY(Sprite sprite, double y); + +double spriteGetScale(Sprite sprite); +void spriteSetScale(Sprite sprite, double scale); + +double spriteGetWidth(Sprite sprite); +void spriteSetWidth(Sprite sprite, double width); + +double spriteGetHeight(Sprite sprite); +void spriteSetHeight(Sprite sprite, double height); + +int spriteGetRotateToDirection(Sprite sprite); +void spriteSetRotateToDirection(Sprite sprite, int rotateToDirection); + +double spriteGetRotation(Sprite sprite); +void spriteSetRotation(Sprite sprite, double rotation); + +double spriteGetRotationSpeed(Sprite sprite); +void spriteSetRotationSpeed(Sprite sprite, double rotationSpeed); + +int spriteGetMirrorX(Sprite sprite); +void spriteSetMirrorX(Sprite sprite, int mirrorX); + +int spriteGetMirrorY(Sprite sprite); +void spriteSetMirrorY(Sprite sprite, int mirrorY); + +double spriteGetDepth(Sprite sprite); +void spriteSetDepth(Sprite sprite, double depth); + +int spriteGetVisible(Sprite sprite); +void spriteSetVisible(Sprite sprite, int visible); + +int spriteGetDebug(Sprite sprite); +void spriteSetDebug(Sprite sprite, int debug); + +int spriteOverlap(Sprite a, Sprite b); +int spriteCollide(Sprite a, Sprite b, double bounciness); +int spriteBounceOff(Sprite sprite, Sprite other, double bounciness); +int spritePush(Sprite sprite, Sprite other, double bounciness); +int spriteCollideEx(Sprite a, Sprite b, int keepVelocityA, int keepVelocityB, double bounciness); + +double spriteGetBounciness(Sprite sprite); +void spriteSetBounciness(Sprite sprite, double bounciness); + +void spriteSetCollider(Sprite sprite, Collider type, double xOffset, double yOffset); +void spriteSetColliderEx(Sprite sprite, Collider type, double xOffset, double yOffset, + double widthOrRadius, double height, double rotationOffset); + +void spriteSetAnimation(Sprite sprite, const char *path); +void spritePlay(Sprite sprite); +void spritePause(Sprite sprite); +void spriteNextFrame(Sprite sprite); +void spriteSetFrame(Sprite sprite, int frame); +void spriteSetShapeColor(Sprite sprite, const char *color); +void spriteSetTintColor(Sprite sprite, const char *color); + +void spriteSetVelocityXY(Sprite sprite, double x, double y); +void spriteSetSpeedAndDirection(Sprite sprite, double speed, double angle); +double spriteGetSpeed(Sprite sprite); +double spriteGetDirection(Sprite sprite); +void spritePointTo(Sprite sprite, double x, double y); + +double spriteGetScaledWidth(Sprite sprite); +double spriteGetScaledHeight(Sprite sprite); + + +#endif diff --git a/src/test.c b/src/test.c new file mode 100644 index 0000000..713f2cd --- /dev/null +++ b/src/test.c @@ -0,0 +1,60 @@ + +#include "private.h" + +//#define HELI_DO_TESTS + +#ifndef HELI_DO_TESTS +void heliDoTests() { } +#else + + +static printStringset(HeliArray *a) { + for(int i = 0; i < a->count; ++i) + printf(" %s\n", (const char*)a->items[i].key); +} + +static void testStringSet() { + printf("-- testStringSet\n"); + + static char *strings[] = { + "hello", + "apple", + "orange", + "pineapple", + "potato", + "carrot", + "grape", + "cucumber" + }; + + HeliArray a = {}; + for(int i = 0; i < (int)(sizeof(strings)/sizeof(*strings)); ++i) + heliStringmapAdd(&a, strings[i], NULL, NULL); + + printf(" fill:\n"); + printStringset(&a); + + printf("\n -remove %s:\n", "hello"); + heliStringmapRemove(&a, "hello"); + printStringset(&a); + + printf("\n -remove %s:\n", "apple"); + heliStringmapRemove(&a, "apple"); + printStringset(&a); + + printf("\n -remove %s:\n", "potato"); + heliStringmapRemove(&a, "potato"); + printStringset(&a); + + heliArrayDestroy(&a); + + printf("-- end\n"); +} + +void heliDoTests() { + printf("---- doTests\n"); + testStringSet(); + printf("---- end\n"); +} + +#endif diff --git a/src/world.c b/src/world.c new file mode 100644 index 0000000..a7b9896 --- /dev/null +++ b/src/world.c @@ -0,0 +1,322 @@ + +#include +#include +#include + +#include "private.h" + +#include "world.h" + + +static GtkWidget *window; + +static int started; +static int stopped; +static unsigned long long frameCount; +static gint64 startTime; +static gint64 currentTime; + +static Callback initCallback; +static Callback drawCallback; + +static cairo_surface_t *cairoSurface; +static int width = 400; +static int height = 400; +static double frameRate = 30; + +static int cameraEnabled; +static double cameraX; +static double cameraY; +static double cameraZoom = 1; + +static HeliArray keysPressed; +static HeliArray keysPressedInFrame; +static HeliArray keysReleasedInFrame; +static HeliArray buttonsPressed; +static HeliArray buttonsPressedInFrame; +static HeliArray buttonsReleasedInFrame; + +static int mouseMovedInFrame; +static double _mouseX; +static double _mouseY; + +static char* buttonNames[] = { + "left", + "middle", + "right" }; +static int buttonsCount = (int)(sizeof(buttonNames)/sizeof(*buttonNames)); + + + +int keyDown(const char *code) + { return heliStringmapGet(&keysPressed, code) != NULL; } +int keyWentDown(const char *code) + { return heliStringmapGet(&keysPressedInFrame, code) != NULL; } +int keyWentUp(const char *code) + { return heliStringmapGet(&keysReleasedInFrame, code) != NULL; } + +int mouseDidMove() + { return mouseMovedInFrame; } +int mouseDown(const char *code) + { return heliStringmapGet(&buttonsPressed, code) != NULL; } +int mouseWentDown(const char *code) + { return heliStringmapGet(&buttonsPressedInFrame, code) != NULL; } +int mouseWentUp(const char *code) + { return heliStringmapGet(&buttonsReleasedInFrame, code) != NULL; } +double mouseX() + { return _mouseX; } +double mouseY() + { return _mouseY; } + +int mousePressedOver(Sprite sprite) + { return buttonsPressed.count && mouseIsOver(sprite); } + +static void resize(int w, int h) { + width = w > 200 ? w : 100; + height = h > 200 ? h : 200; + if (started) { + gtk_window_set_default_size(GTK_WINDOW(window), width, height); + gtk_widget_set_size_request(window, width, height); + } +} + +int worldGetWidth() + { return width; } +void worldSetWidth(int w) + { resize(w, height); } + +int worldGetHeight() + { return height; } +void worldSetHeight(int h) + { resize(width, h); } + +double worldGetFrameRate() + { return frameRate; } +void worldSetFrameRate(double frameRate) { + if (!(frameRate > 1)) frameRate = 1; + if (!(frameRate < 100)) frameRate = 100; +} + +double worldGetTimeStep() + { return 1/frameRate; } + +int worldGetFrameCount() + { return (int)frameCount; } +double worldGetSeconds() + { return started ? (currentTime - startTime)*1e-6 : 0.0; } + +void worldSetInit(Callback init) + { initCallback = init; } +void worldSetDraw(Callback draw) + { drawCallback = draw; } +void worldStop() + { if (started) stopped = TRUE; } + + +void cameraOn() + { cameraEnabled = TRUE; } +void cameraOff() + { cameraEnabled = FALSE; } +int cameraIsActive() + { return cameraEnabled; } + +double cameraMouseX() + { return _mouseX/cameraZoom + cameraX; } +double cameraMouseY() + { return _mouseY/cameraZoom + cameraY; } + +double cameraGetX() + { return cameraX; } +void cameraSetX(double x) + { cameraX = x; } + +double cameraGetY() + { return cameraY; } +void cameraSetY(double y) + { cameraY = y; } + +double cameraGetZoom() + { return cameraZoom; } +void cameraSetZoom(double x) + { cameraZoom = x > 1e-6 ? x : 1e-6; } + +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; + + 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 (doFrame) { + if (frameCount == 0) heliDoTests(); + + heliCairo = cairo_create(cairoSurface); + heliDrawingPrepareFrame(); + if (drawCallback) drawCallback(); + cairo_surface_flush(cairoSurface); + cairo_destroy(heliCairo); + heliCairo = NULL; + + currentTime += timeStep; + ++frameCount; + heliSpriteUpdate(dt); + heliSoundUpdate(); + + heliArrayClear(&keysPressedInFrame); + heliArrayClear(&keysReleasedInFrame); + heliArrayClear(&buttonsPressedInFrame); + heliArrayClear(&buttonsReleasedInFrame); + + if (stopped) g_idle_add_full(G_PRIORITY_DEFAULT, idle, NULL, NULL); + } + + gint64 delta = newTime - currentTime - 2000000; + if (delta > 0) { + startTime += delta; + currentTime += delta; + } + + heliDrawingClearFrame(cr); + cairo_save(cr); + cairo_set_source_surface(cr, cairoSurface, 0, 0); + cairo_paint(cr); + cairo_restore(cr); + + if (!stopped) gtk_widget_queue_draw(window); + + delta = currentTime - g_get_monotonic_time(); + if (delta > 100) g_usleep(delta - 100); + + 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); + heliStringmapAdd(&keysPressed, keyname, NULL, NULL); + heliStringmapAdd(&keysPressedInFrame, keyname, NULL, NULL); + free(keyname); + return TRUE; + case GDK_KEY_RELEASE: + keyname = heliStringCopy( gdk_keyval_name(event->key.keyval) ); + heliLowercase(keyname); + heliStringmapRemove(&keysPressed, keyname); + heliStringmapAdd(&keysReleasedInFrame, keyname, NULL, NULL); + free(keyname); + return TRUE; + case GDK_BUTTON_PRESS: + button = event->button.button; + if (button >= 1 && button <= buttonsCount) { + heliStringmapAdd(&buttonsPressed, buttonNames[button - 1], NULL, NULL); + heliStringmapAdd(&buttonsPressedInFrame, buttonNames[button - 1], NULL, NULL); + } + _mouseX = event->button.x; + _mouseY = event->button.y; + return TRUE; + case GDK_BUTTON_RELEASE: + button = event->button.button; + if (button >= 1 && button <= buttonsCount) { + heliStringmapRemove(&buttonsPressed, buttonNames[button - 1]); + heliStringmapAdd(&buttonsReleasedInFrame, buttonNames[button - 1], NULL, NULL); + } + _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_DELETE: + stopped = TRUE; + window = NULL; + break; + default: + break; + } + 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), FALSE); + gtk_window_set_default_size(GTK_WINDOW(window), width, height); + 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 ); + gtk_widget_set_size_request(window, width, height); + gtk_widget_show(window); + + currentTime = startTime = 0; + if (initCallback) initCallback(); + g_idle_add(idle, NULL); +} + +void worldRun() { + if (started) return; + started = TRUE; + stopped = FALSE; + + frameCount = 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); + + heliSpriteFinish(); + heliDrawingFinish(); + heliAnimationFinish(); + heliSoundFinish(); + + if (cairoSurface) cairo_surface_destroy(cairoSurface); + cairoSurface = NULL; + + heliArrayDestroy(&keysPressed); + heliArrayDestroy(&keysPressed); + heliArrayDestroy(&keysPressedInFrame); + heliArrayDestroy(&keysReleasedInFrame); + heliArrayDestroy(&buttonsPressed); + heliArrayDestroy(&buttonsPressedInFrame); + heliArrayDestroy(&buttonsReleasedInFrame); + + started = FALSE; +} diff --git a/src/world.h b/src/world.h new file mode 100644 index 0000000..c01e67f --- /dev/null +++ b/src/world.h @@ -0,0 +1,74 @@ +#ifndef HELI_WORLD_H +#define HELI_WORLD_H + + +#include "common.h" +#include "sprite.h" + +typedef void (*Callback)(); +typedef struct _Sound *Sound; + + +void drawSprites(); + +Sound createSound(const char *path); +void soundDestroy(Sound sound); +void soundPlay(Sound sound, int loop); +void soundStop(Sound sound); + +int keyDown(const char *code); +int keyWentDown(const char *code); +int keyWentUp(const char *code); + +int mouseDidMove(); +int mouseDown(const char *code); +int mouseWentDown(const char *code); +int mouseWentUp(const char *code); +double mouseX(); +double mouseY(); + +int mouseIsOver(Sprite sprite); +int mousePressedOver(Sprite sprite); + + +int worldGetSpriteCount(); +Sprite worldGetSprite(int i); + +int worldGetWidth(); +void worldSetWidth(int width); + +int worldGetHeight(); +void worldSetHeight(int height); + +double worldGetFrameRate(); +void worldSetFrameRate(double frameRate); + +double worldGetTimeStep(); + +int worldGetFrameCount(); +double worldGetSeconds(); + +void worldSetInit(Callback init); +void worldSetDraw(Callback draw); +void worldRun(); +void worldStop(); + + +void cameraOn(); +void cameraOff(); +int cameraIsActive(); + +double cameraMouseX(); +double cameraMouseY(); + +double cameraGetX(); +void cameraSetX(double x); + +double cameraGetY(); +void cameraSetY(double y); + +double cameraGetZoom(); +void cameraSetZoom(double zoom); + + +#endif diff --git a/test/main.c b/test/main.c deleted file mode 100644 index 5410171..0000000 --- a/test/main.c +++ /dev/null @@ -1,15 +0,0 @@ - -#include - -void init() { -} - -void draw() { -} - -int main() { - worldSetInit(&init); - worldSetDraw(&draw); - worldRun(); - return 0; -}