diff --git a/demo/src/main.c b/demo/src/main.c index 06e17a7..5965b68 100644 --- a/demo/src/main.c +++ b/demo/src/main.c @@ -36,6 +36,10 @@ void draw() { if (mouseWentDown("left")) soundPlay(beep, FALSE); + spriteCollide(ball, brick1, 1); + spriteCollide(ball, brick2, 1); + spriteCollide(brick2, brick1, 1); + drawSprites(); } diff --git a/src/animation.c b/src/animation.c index f5c3d83..f38f72e 100644 --- a/src/animation.c +++ b/src/animation.c @@ -29,8 +29,8 @@ static cairo_surface_t* loadFrame(const char *path) { return NULL; } -static HeliAnimationInstance* load(const char *path) { - HeliAnimationInstance *a = calloc(1, sizeof(*a)); +static HeliAnimation* load(const char *path) { + HeliAnimation *a = calloc(1, sizeof(*a)); a->path = heliStringCopy(path); GDir *dir = g_dir_open(path, 0, NULL); @@ -55,31 +55,24 @@ static HeliAnimationInstance* load(const char *path) { return a; } -static void unload(HeliAnimationInstance *a) { +static void unload(HeliAnimation *a) { assert(!a->refcount); free(a->path); heliArrayDestroy(&a->frames); free(a); } -Animation createAnimation(const char *path) { +HeliAnimation* heliAnimationLoad(const char *path) { HeliPair *item = heliStringmapGet(&cache, path); if (!item) item = heliStringmapAdd(&cache, path, load(path), (HeliFreeCallback)&unload); - HeliAnimationInstance *a = (HeliAnimationInstance*)item->value; + HeliAnimation *a = (HeliAnimation*)item->value; ++a->refcount; - - Animation animation = calloc(1, sizeof(*animation)); - animation->instance = a; - ++animation->refcount; - return animation; + return a; } -void animationDestroy(Animation animation) { - if (--animation->refcount <= 0) { - if (--animation->instance->refcount <= 0) - heliStringmapRemove(&cache, animation->instance->path); - free(animation); - } +void heliAnimationUnref(HeliAnimation *a) { + if (--a->refcount <= 0) + heliStringmapRemove(&cache, a->path); } void heliAnimationFinish() diff --git a/src/collider.c b/src/collider.c index 500cc28..d75f7e3 100644 --- a/src/collider.c +++ b/src/collider.c @@ -1,12 +1,219 @@ #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; + +typedef struct _HeliCollisionData { + int flip; + double dirx, diry; + double radius; + double x1, y1, x2, y2; + double lmin, lmax; + double nxMin, nyMin; + double nxMax, nyMax; + int found; +} HeliCollisionData; + +static void applyPoint(HeliCollisionData *data, double l, double nx, double ny) { + if (data->flip) { + l = -l; + nx = -nx; + ny = -ny; + } + if (!data->found || l < data->lmin) { + data->lmin = l; + data->nxMin = nx; + data->nyMin = ny; + } + if (!data->found || l > data->lmax) { + data->lmax = l; + data->nxMax = nx; + data->nyMax = ny; + } + data->found = TRUE; +} + + +static void collision(HeliCollisionData *data) { + double A = data->x2 - data->x1; + double B = data->y2 - data->y1; + double AB2 = A*A + B*B; + if (AB2 <= HELI_PRECISION_SQR) { + // circle with circle + if (fabs(data->radius) > HELI_PRECISION) { + A = data->dirx*data->dirx + data->diry*data->diry; + if (A > HELI_PRECISION_SQR) { + B = -2*(data->dirx * data->x1 + data->diry * data->y1); + double C = data->x1*data->x1 + data->y1*data->y1 - data->radius*data->radius; + double D = B*B - 4*A*C; + + double l[2]; + int count = 0; + if (fabs(D) <= HELI_PRECISION_SQR*HELI_PRECISION_SQR) { + count = 1; + l[0] = -0.5*B/A; + } else + if (D > 0) { + count = 2; + double d = sqrt(D); + double ka = 0.5/A; + l[0] = (-B - d)*ka; + l[1] = (-B + d)*ka; + } + + double kr = 1/data->radius; + for(int i = 0; i < count; ++i) { + double nx = (l[i]*data->dirx - data->x1)*kr; + double ny = (l[i]*data->diry - data->y1)*kr; + applyPoint(data, l[i], nx, ny); + } + } + } + } else { + // circle with line + double div = B*data->dirx - A*data->diry; + if (fabs(div) > HELI_PRECISION) { + double mult = 1/div; + + double kn = 1/sqrt(AB2); + double nx = -B*kn; + double ny = A*kn; + double kx = nx*fabs(data->radius) - data->x1; + double ky = ny*fabs(data->radius) - data->y1; + + double ll = (ky*data->dirx - kx*data->diry)*mult; + if (ll >= -HELI_PRECISION && ll <= 1 + HELI_PRECISION) { + double l = (ky*A - kx*B)*mult; + applyPoint(data, l, nx, ny); + } + } + } +}; + +static void calcCorners(HeliCollider *c, double *corners) { + double a = c->rotation*(PI/180); + double cn = cos(a); + double sn = sin(a); + + double wx = cn*fabs(c->width); + double wy = sn*fabs(c->width); + double hx = -sn*fabs(c->height); + double hy = cn*fabs(c->height); + + corners[0] = wx + hx + c->x; + corners[1] = wy + hy + c->y; + + corners[2] = wx - hx + c->x; + corners[3] = wy - hy + c->y; + + corners[4] = -wx - hx + c->x; + corners[5] = -wy - hy + c->y; + + corners[6] = -wx + hx + c->x; + corners[7] = -wy + hy + c->y; +} + +static void collisionCorners(HeliCollisionData *data, double *corners, double x, double y) { + for(int i = 0; i < 4; ++i) { + int j = (i + 1)%4; + data->x1 = corners[i+0] - x; + data->y1 = corners[i+1] - y; + data->x2 = corners[j+0] - x; + data->y2 = corners[j+1] - y; + collision(data); + data->x2 = data->x1; + data->y2 = data->y1; + collision(data); + } +} + + +int heliCheckCollision( + HeliCollider *a, HeliCollider *b, + double *dx, double *dy, + double *vx, double *vy, + double bounciness ) +{ + HeliCollisionData data = {}; + + // calc "moving" direction + data.dirx = *vx; + data.diry = *vy; + double k = data.dirx*data.dirx + data.diry*data.diry; + if (k <= HELI_PRECISION_SQR) { + data.dirx = b->x - a->x; + data.diry = b->y - a->y; + k = data.dirx*data.dirx + data.diry*data.diry; + } + + if (k <= HELI_PRECISION_SQR) { + data.dirx = 1; + data.diry = 0; + } else { + k = 1/sqrt(k); + data.dirx *= k; + data.diry *= k; + } + + // check collision + if (a->type == COLLIDER_CIRCLE && b->type == COLLIDER_CIRCLE) { + data.radius = fabs(a->radius) + fabs(b->radius); + data.x1 = data.x2 = b->x - a->x; + data.y1 = data.y2 = b->y - a->y; + collision(&data); + } else + if (a->type == COLLIDER_CIRCLE && b->type == COLLIDER_RECTANGLE) { + double corners[8]; + calcCorners(b, corners); + data.radius = fabs(a->radius) + fabs(b->radius); + collisionCorners(&data, corners, a->x, a->y); + } else + if (a->type == COLLIDER_RECTANGLE && b->type == COLLIDER_CIRCLE) { + double corners[8]; + calcCorners(a, corners); + data.flip = TRUE; + data.radius = fabs(a->radius) + fabs(b->radius); + collisionCorners(&data, corners, b->x, b->y); + } else + if (a->type == COLLIDER_RECTANGLE && b->type == COLLIDER_RECTANGLE) { + double cornersA[8], cornersB[8]; + calcCorners(a, cornersA); + calcCorners(b, cornersB); + data.radius = fabs(a->radius) + fabs(b->radius); + for(int i = 0; i < 4; ++i) + collisionCorners(&data, cornersB, cornersA[i+0], cornersA[i+1]); + data.flip = TRUE; + for(int i = 0; i < 4; ++i) + collisionCorners(&data, cornersA, cornersB[i+0], cornersB[i+1]); + } + + // is collision found? + *dx = 0; *dy = 0; + if ( !data.found + || data.lmin >= -HELI_PRECISION + || data.lmax <= HELI_PRECISION ) + return FALSE; + + // calc displacement and velocity + double l, nx, ny; + if (-data.lmin > data.lmax) { + l = -data.lmin; + nx = data.nxMin; + ny = data.nyMin; + } else { + l = data.lmax; + nx = data.nxMax; + ny = data.nyMax; + } + + *dx = data.dirx*l; + *dy = data.diry*l; + double kn = *vx*nx + *vy*ny; + if (kn < 0) { + *vx -= 2*kn*fabs(bounciness)*nx; + *vy -= 2*kn*fabs(bounciness)*ny; + } + + return TRUE; } int heliPointCollision(HeliCollider *c, double x, double y) { diff --git a/src/group.c b/src/group.c index e9c4b2c..8bef81b 100644 --- a/src/group.c +++ b/src/group.c @@ -178,18 +178,13 @@ void groupSetMirrorYEach(Group group, int mirrorY) { foreachInt(group, mirrorY, &spriteSetMirrorY); } void groupSetTagEach(Group group, int tag) { foreachInt(group, tag, &spriteSetTag); } -void groupSetAnimationPathEach(Group group, const char *path) - { foreachString(group, path, &spriteSetAnimationPath); } +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 groupSetAnimationEach(Group group, Animation animation) { - for(int i = 0; i < groupGetCount(group); ++i) - spriteSetAnimation(groupGet(group, i), animation); -} - void groupSetNoAnimationEach(Group group) { for(int i = 0; i < groupGetCount(group); ++i) spriteSetNoAnimation(groupGet(group, i)); diff --git a/src/group.h b/src/group.h index 84fe917..124176f 100644 --- a/src/group.h +++ b/src/group.h @@ -58,8 +58,7 @@ void groupSetMirrorYEach(Group group, int mirrorY); void groupSetTagEach(Group group, int tag); void groupPointToEach(Group group, double x, double y); void groupSetSpeedAndDirectionEach(Group group, double speed, double angle); -void groupSetAnimationEach(Group group, Animation animation); -void groupSetAnimationPathEach(Group group, const char *path); +void groupSetAnimationEach(Group group, const char *path); void groupSetNoAnimationEach(Group group); void groupSetShapeColorEach(Group group, const char *color); void groupSetTintColorEach(Group group, const char *color); diff --git a/src/private.h b/src/private.h index 56857ac..4509224 100644 --- a/src/private.h +++ b/src/private.h @@ -14,6 +14,9 @@ // globals +#define HELI_PRECISION 1e-6 +#define HELI_PRECISION_SQR 1e-12 + extern cairo_t *heliCairo; @@ -64,17 +67,14 @@ int heliStringmapRemove(HeliArray *a, const char *k); // animation -typedef struct _HeliAnimationInastance { +typedef struct _HeliAnimation { char *path; HeliArray frames; int refcount; -} HeliAnimationInstance; - -struct _Animation { - HeliAnimationInstance *instance; - int refcount; -}; +} HeliAnimation; +HeliAnimation* heliAnimationLoad(const char *path); +void heliAnimationUnref(HeliAnimation *a); void heliAnimationFinish(); @@ -90,7 +90,12 @@ typedef struct _HeliCollider { double rotation; } HeliCollider; -int heliCheckCollision(HeliCollider *a, HeliCollider *b, double *normX, double *normY); +int heliCheckCollision( + HeliCollider *a, HeliCollider *b, + double *dx, double *dy, + double *vx, double *vy, + double bounciness ); + int heliPointCollision(HeliCollider *c, double x, double y); diff --git a/src/sprite.c b/src/sprite.c index 107e127..c1fd971 100644 --- a/src/sprite.c +++ b/src/sprite.c @@ -39,7 +39,7 @@ struct _Sprite { HeliArray groups; - Animation animation; + HeliAnimation *animation; }; static HeliArray sprites; @@ -50,10 +50,18 @@ 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->width = sprite->collider.width; + collider->height = sprite->collider.height; + collider->radius = sprite->collider.radius; collider->rotation = sprite->collider.rotation + sprite->rotation; + + if (collider->width < 0) collider->width = sprite->width; + if (collider->height < 0) collider->height = sprite->height; + if (collider->radius < 0) collider->radius = sprite->width; + + collider->width *= sprite->scale; + collider->height *= sprite->scale; + collider->radius *= sprite->scale; } @@ -88,6 +96,7 @@ Sprite createSprite(double x, double y) { return createSpriteEx(x, y, 100, 100); } void spriteDestroy(Sprite sprite) { + spriteSetNoAnimation(sprite); while(sprite->groups.count > 0) groupRemove((Group)sprite->groups.items[0].value, sprite); for(int i = 0; i < sprites.count; ++i) @@ -96,8 +105,6 @@ void spriteDestroy(Sprite sprite) { for(int i = 0; i < spritesSorted.count; ++i) if (spritesSorted.items[i].value == sprite) { heliArrayRemove(&spritesSorted, i); break; } - if (sprite->animation) - animationDestroy(sprite->animation); free(sprite); } @@ -183,8 +190,44 @@ 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; + HeliCollider ca, cb; + prepareCollider(a, &ca); + prepareCollider(b, &cb); + double dx = 0, dy = 0; + double vx = a->vx - b->vx; + double vy = a->vy - b->vy; + bounciness = fabs(bounciness * a->bounciness * b->bounciness); + if (!heliCheckCollision(&ca, &cb, &dx, &dy, &vx, &vy, bounciness)) + return FALSE; + + if ( keepVelocityA && !keepVelocityB) { + b->x -= dx; + b->y -= dy; + b->vx = a->vx - vx; + b->vy = a->vy - vy; + } else + if (!keepVelocityA && keepVelocityB) { + a->x += dx; + a->y += dy; + a->vx = b->vx + vx; + a->vy = b->vy + vy; + } else + if (!keepVelocityA && !keepVelocityB) { + double vxAvg = 0.5*(a->vx + b->vx); + double vyAvg = 0.5*(a->vy + b->vy); + + a->x += 0.5*dx; + a->y += 0.5*dy; + a->vx = vxAvg + 0.5*vx; + a->vy = vyAvg + 0.5*vy; + + b->x -= 0.5*dx; + b->y -= 0.5*dy; + b->vx = vxAvg - 0.5*vx; + b->vy = vyAvg - 0.5*vy; + } + + return TRUE; } @@ -206,33 +249,25 @@ void spriteSetColliderEx(Sprite sprite, Collider type, double xOffset, double yO void spriteSetCollider(Sprite sprite, Collider type, double xOffset, double yOffset) { spriteSetColliderEx(sprite, type, xOffset, yOffset, -1, -1, 0); } -void spriteSetAnimation(Sprite sprite, Animation animation) { - Animation prev = sprite->animation; - sprite->animation = animation; - if (animation) ++animation->refcount; - if (prev) animationDestroy(prev); +void spriteSetAnimation(Sprite sprite, const char *path) { + HeliAnimation *prev = sprite->animation; + sprite->animation = heliAnimationLoad(path); + if (prev) heliAnimationUnref(prev); spriteSetFrame(sprite, sprite->frame); } -void spriteSetAnimationPath(Sprite sprite, const char *path) { - Animation animation = createAnimation(path); - spriteSetAnimation(sprite, animation); - animationDestroy(animation); +void spriteSetNoAnimation(Sprite sprite) { + if (sprite->animation) heliAnimationUnref(sprite->animation); + sprite->animation = NULL; } -void spriteSetNoAnimation(Sprite sprite) - { spriteSetAnimation(sprite, NULL); } - -Animation spriteGetAnimation(Sprite sprite) - { return sprite->animation; } - 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->instance->frames.count > 0 - ? frame % sprite->animation->instance->frames.count : 0; + sprite->frame = frame >= 0 && sprite->animation && sprite->animation->frames.count > 0 + ? frame % sprite->animation->frames.count : 0; } void spriteSetShapeColor(Sprite sprite, const char *color) @@ -255,7 +290,7 @@ void spriteSetSpeedAndDirection(Sprite sprite, double speed, double angle) { 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 + return fabs(sprite->vx) > HELI_PRECISION || fabs(sprite->vy) > HELI_PRECISION ? atan2(sprite->vy, sprite->vx)*(180/PI) : 0; } void spritePointTo(Sprite sprite, double x, double y) @@ -322,14 +357,14 @@ static void drawSprite(cairo_t *cr, Sprite s) { double hh = s->height*0.5; cairo_surface_t *frame = s->animation - ? (cairo_surface_t*)heliArrayGetValue(&s->animation->instance->frames, s->frame) : NULL; + ? (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; + int tint = fabs(s->tintColor[0] - 1) > HELI_PRECISION + || fabs(s->tintColor[1] - 1) > HELI_PRECISION + || fabs(s->tintColor[2] - 1) > HELI_PRECISION; + int alpha = fabs(a) > HELI_PRECISION; + int visible = fabs(a) > HELI_PRECISION; if (visible) { if (tint) cairo_push_group(cr); @@ -338,7 +373,7 @@ static void drawSprite(cairo_t *cr, Sprite s) { // 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) { + if (ihw > HELI_PRECISION && ihh > HELI_PRECISION) { cairo_save(cr); cairo_scale(cr, hw/ihw, hh/ihh); cairo_set_source_surface(cr, frame, -ihw, -ihh); @@ -428,9 +463,9 @@ 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) { + if (s->lifeTime >= -HELI_PRECISION) { s->lifeTime -= dt; - if (s->lifeTime <= 1e-5) + if (s->lifeTime <= HELI_PRECISION) spriteDestroy(s); } } diff --git a/src/sprite.h b/src/sprite.h index 98f8112..a3291a2 100644 --- a/src/sprite.h +++ b/src/sprite.h @@ -5,7 +5,6 @@ #include "common.h" typedef struct _Sprite *Sprite; -typedef struct _Animation *Animation; typedef enum _Collider { @@ -14,10 +13,6 @@ typedef enum _Collider { } Collider; -Animation createAnimation(const char *path); -void animationDestroy(); - - Sprite createSprite(double x, double y); Sprite createSpriteEx(double x, double y, double width, double height); @@ -85,11 +80,8 @@ void spriteSetCollider(Sprite sprite, Collider type, double xOffset, double yOff void spriteSetColliderEx(Sprite sprite, Collider type, double xOffset, double yOffset, double widthOrRadius, double height, double rotationOffset); -void spriteSetAnimation(Sprite sprite, Animation animation); +void spriteSetAnimation(Sprite sprite, const char *path); void spriteSetNoAnimation(Sprite sprite); -void spriteSetAnimationPath(Sprite sprite, const char *path); -Animation spriteGetAnimation(Sprite sprite); - void spritePlay(Sprite sprite); void spritePause(Sprite sprite); void spriteNextFrame(Sprite sprite); diff --git a/src/world.c b/src/world.c index a7b9896..50d71c2 100644 --- a/src/world.c +++ b/src/world.c @@ -103,7 +103,7 @@ double worldGetTimeStep() int worldGetFrameCount() { return (int)frameCount; } double worldGetSeconds() - { return started ? (currentTime - startTime)*1e-6 : 0.0; } + { return started ? (currentTime - startTime)*HELI_PRECISION : 0.0; } void worldSetInit(Callback init) { initCallback = init; } @@ -138,7 +138,7 @@ void cameraSetY(double y) double cameraGetZoom() { return cameraZoom; } void cameraSetZoom(double x) - { cameraZoom = x > 1e-6 ? x : 1e-6; } + { cameraZoom = x > HELI_PRECISION ? x : HELI_PRECISION; } static gboolean idle(gpointer data G_GNUC_UNUSED) { if (stopped) {