|
|
8535a3 |
|
|
|
8535a3 |
#include "private.h"
|
|
|
8535a3 |
|
|
|
1c7488 |
|
|
|
1c7488 |
typedef struct _HeliCollisionData {
|
|
|
1c7488 |
int flip;
|
|
|
1c7488 |
double dirx, diry;
|
|
|
1c7488 |
double radius;
|
|
|
1c7488 |
double x1, y1, x2, y2;
|
|
|
1c7488 |
double lmin, lmax;
|
|
|
1c7488 |
double nxMin, nyMin;
|
|
|
1c7488 |
double nxMax, nyMax;
|
|
|
1c7488 |
int found;
|
|
|
1c7488 |
} HeliCollisionData;
|
|
|
1c7488 |
|
|
|
1c7488 |
static void applyPoint(HeliCollisionData *data, double l, double nx, double ny) {
|
|
|
1c7488 |
if (data->flip) {
|
|
|
1c7488 |
l = -l;
|
|
|
1c7488 |
nx = -nx;
|
|
|
1c7488 |
ny = -ny;
|
|
|
1c7488 |
}
|
|
|
1c7488 |
if (!data->found || l < data->lmin) {
|
|
|
1c7488 |
data->lmin = l;
|
|
|
1c7488 |
data->nxMin = nx;
|
|
|
1c7488 |
data->nyMin = ny;
|
|
|
1c7488 |
}
|
|
|
1c7488 |
if (!data->found || l > data->lmax) {
|
|
|
1c7488 |
data->lmax = l;
|
|
|
1c7488 |
data->nxMax = nx;
|
|
|
1c7488 |
data->nyMax = ny;
|
|
|
1c7488 |
}
|
|
|
1c7488 |
data->found = TRUE;
|
|
|
1c7488 |
}
|
|
|
1c7488 |
|
|
|
1c7488 |
|
|
|
1c7488 |
static void collision(HeliCollisionData *data) {
|
|
|
1c7488 |
double A = data->x2 - data->x1;
|
|
|
1c7488 |
double B = data->y2 - data->y1;
|
|
|
1c7488 |
double AB2 = A*A + B*B;
|
|
|
1c7488 |
if (AB2 <= HELI_PRECISION_SQR) {
|
|
|
1c7488 |
// circle with circle
|
|
|
1c7488 |
if (fabs(data->radius) > HELI_PRECISION) {
|
|
|
1c7488 |
A = data->dirx*data->dirx + data->diry*data->diry;
|
|
|
1c7488 |
if (A > HELI_PRECISION_SQR) {
|
|
|
1c7488 |
B = -2*(data->dirx * data->x1 + data->diry * data->y1);
|
|
|
1c7488 |
double C = data->x1*data->x1 + data->y1*data->y1 - data->radius*data->radius;
|
|
|
1c7488 |
double D = B*B - 4*A*C;
|
|
|
1c7488 |
|
|
|
1c7488 |
double l[2];
|
|
|
1c7488 |
int count = 0;
|
|
|
1c7488 |
if (fabs(D) <= HELI_PRECISION_SQR*HELI_PRECISION_SQR) {
|
|
|
1c7488 |
count = 1;
|
|
|
1c7488 |
l[0] = -0.5*B/A;
|
|
|
1c7488 |
} else
|
|
|
1c7488 |
if (D > 0) {
|
|
|
1c7488 |
count = 2;
|
|
|
1c7488 |
double d = sqrt(D);
|
|
|
1c7488 |
double ka = 0.5/A;
|
|
|
1c7488 |
l[0] = (-B - d)*ka;
|
|
|
1c7488 |
l[1] = (-B + d)*ka;
|
|
|
1c7488 |
}
|
|
|
1c7488 |
|
|
|
1c7488 |
double kr = 1/data->radius;
|
|
|
1c7488 |
for(int i = 0; i < count; ++i) {
|
|
|
1c7488 |
double nx = (l[i]*data->dirx - data->x1)*kr;
|
|
|
1c7488 |
double ny = (l[i]*data->diry - data->y1)*kr;
|
|
|
1c7488 |
applyPoint(data, l[i], nx, ny);
|
|
|
1c7488 |
}
|
|
|
1c7488 |
}
|
|
|
1c7488 |
}
|
|
|
1c7488 |
} else {
|
|
|
1c7488 |
// circle with line
|
|
|
1c7488 |
double div = B*data->dirx - A*data->diry;
|
|
|
f8c1ea |
if (fabs(div) > HELI_PRECISION_SQR) {
|
|
|
1c7488 |
double mult = 1/div;
|
|
|
1c7488 |
|
|
|
1c7488 |
double kn = 1/sqrt(AB2);
|
|
|
1c7488 |
double nx = -B*kn;
|
|
|
1c7488 |
double ny = A*kn;
|
|
|
f8c1ea |
double kx = -nx*fabs(data->radius) - data->x1;
|
|
|
f8c1ea |
double ky = -ny*fabs(data->radius) - data->y1;
|
|
|
1c7488 |
|
|
|
1c7488 |
double ll = (ky*data->dirx - kx*data->diry)*mult;
|
|
|
1c7488 |
if (ll >= -HELI_PRECISION && ll <= 1 + HELI_PRECISION) {
|
|
|
1c7488 |
double l = (ky*A - kx*B)*mult;
|
|
|
1c7488 |
applyPoint(data, l, nx, ny);
|
|
|
1c7488 |
}
|
|
|
1c7488 |
}
|
|
|
1c7488 |
}
|
|
|
1c7488 |
};
|
|
|
1c7488 |
|
|
|
1c7488 |
static void calcCorners(HeliCollider *c, double *corners) {
|
|
|
1c7488 |
double a = c->rotation*(PI/180);
|
|
|
1c7488 |
double cn = cos(a);
|
|
|
1c7488 |
double sn = sin(a);
|
|
|
1c7488 |
|
|
|
f8c1ea |
double wx = 0.5*cn*fabs(c->width);
|
|
|
f8c1ea |
double wy = 0.5*sn*fabs(c->width);
|
|
|
f8c1ea |
double hx = -0.5*sn*fabs(c->height);
|
|
|
f8c1ea |
double hy = 0.5*cn*fabs(c->height);
|
|
|
1c7488 |
|
|
|
1c7488 |
corners[0] = wx + hx + c->x;
|
|
|
1c7488 |
corners[1] = wy + hy + c->y;
|
|
|
1c7488 |
|
|
|
1c7488 |
corners[2] = wx - hx + c->x;
|
|
|
1c7488 |
corners[3] = wy - hy + c->y;
|
|
|
1c7488 |
|
|
|
1c7488 |
corners[4] = -wx - hx + c->x;
|
|
|
1c7488 |
corners[5] = -wy - hy + c->y;
|
|
|
1c7488 |
|
|
|
1c7488 |
corners[6] = -wx + hx + c->x;
|
|
|
1c7488 |
corners[7] = -wy + hy + c->y;
|
|
|
1c7488 |
}
|
|
|
1c7488 |
|
|
|
1c7488 |
static void collisionCorners(HeliCollisionData *data, double *corners, double x, double y) {
|
|
|
1c7488 |
for(int i = 0; i < 4; ++i) {
|
|
|
1c7488 |
int j = (i + 1)%4;
|
|
|
f8c1ea |
data->x1 = corners[2*i+0] - x;
|
|
|
f8c1ea |
data->y1 = corners[2*i+1] - y;
|
|
|
f8c1ea |
data->x2 = corners[2*j+0] - x;
|
|
|
f8c1ea |
data->y2 = corners[2*j+1] - y;
|
|
|
1c7488 |
collision(data);
|
|
|
1c7488 |
data->x2 = data->x1;
|
|
|
1c7488 |
data->y2 = data->y1;
|
|
|
1c7488 |
collision(data);
|
|
|
1c7488 |
}
|
|
|
1c7488 |
}
|
|
|
1c7488 |
|
|
|
1c7488 |
|
|
|
1c7488 |
int heliCheckCollision(
|
|
|
f80a0a |
HeliCollider *a,
|
|
|
f80a0a |
HeliCollider *b,
|
|
|
f80a0a |
HeliCollisionInfo *info,
|
|
|
f80a0a |
double distance )
|
|
|
1c7488 |
{
|
|
|
f80a0a |
distance = fabs(distance);
|
|
|
1c7488 |
HeliCollisionData data = {};
|
|
|
1c7488 |
|
|
|
1c7488 |
// calc "moving" direction
|
|
|
f8c1ea |
data.dirx = b->x - a->x;
|
|
|
f8c1ea |
data.diry = b->y - a->y;
|
|
|
1c7488 |
double k = data.dirx*data.dirx + data.diry*data.diry;
|
|
|
1c7488 |
if (k <= HELI_PRECISION_SQR) {
|
|
|
1c7488 |
data.dirx = 1;
|
|
|
1c7488 |
data.diry = 0;
|
|
|
1c7488 |
} else {
|
|
|
1c7488 |
k = 1/sqrt(k);
|
|
|
1c7488 |
data.dirx *= k;
|
|
|
1c7488 |
data.diry *= k;
|
|
|
1c7488 |
}
|
|
|
1c7488 |
|
|
|
1c7488 |
// check collision
|
|
|
1c7488 |
if (a->type == COLLIDER_CIRCLE && b->type == COLLIDER_CIRCLE) {
|
|
|
f80a0a |
data.radius = fabs(a->radius) + fabs(b->radius) + distance;
|
|
|
1c7488 |
data.x1 = data.x2 = b->x - a->x;
|
|
|
1c7488 |
data.y1 = data.y2 = b->y - a->y;
|
|
|
1c7488 |
collision(&data);
|
|
|
1c7488 |
} else
|
|
|
1c7488 |
if (a->type == COLLIDER_CIRCLE && b->type == COLLIDER_RECTANGLE) {
|
|
|
1c7488 |
double corners[8];
|
|
|
1c7488 |
calcCorners(b, corners);
|
|
|
f80a0a |
data.radius = fabs(a->radius) + fabs(b->radius) + distance;
|
|
|
44355f |
if (data.radius > HELI_PRECISION) collisionCorners(&data, corners, a->x, a->y);
|
|
|
1c7488 |
} else
|
|
|
1c7488 |
if (a->type == COLLIDER_RECTANGLE && b->type == COLLIDER_CIRCLE) {
|
|
|
1c7488 |
double corners[8];
|
|
|
1c7488 |
calcCorners(a, corners);
|
|
|
1c7488 |
data.flip = TRUE;
|
|
|
f80a0a |
data.radius = fabs(a->radius) + fabs(b->radius) + distance;
|
|
|
44355f |
if (data.radius > HELI_PRECISION) collisionCorners(&data, corners, b->x, b->y);
|
|
|
1c7488 |
} else
|
|
|
1c7488 |
if (a->type == COLLIDER_RECTANGLE && b->type == COLLIDER_RECTANGLE) {
|
|
|
1c7488 |
double cornersA[8], cornersB[8];
|
|
|
1c7488 |
calcCorners(a, cornersA);
|
|
|
1c7488 |
calcCorners(b, cornersB);
|
|
|
f80a0a |
data.radius = fabs(a->radius) + fabs(b->radius) + distance;
|
|
|
1c7488 |
for(int i = 0; i < 4; ++i)
|
|
|
f8c1ea |
collisionCorners(&data, cornersB, cornersA[2*i+0], cornersA[2*i+1]);
|
|
|
1c7488 |
data.flip = TRUE;
|
|
|
1c7488 |
for(int i = 0; i < 4; ++i)
|
|
|
f8c1ea |
collisionCorners(&data, cornersA, cornersB[2*i+0], cornersB[2*i+1]);
|
|
|
1c7488 |
}
|
|
|
1c7488 |
|
|
|
1c7488 |
// is collision found?
|
|
|
f80a0a |
info->actualCollision = FALSE;
|
|
|
f80a0a |
info->distance = 0;
|
|
|
f80a0a |
info->dx = info->dy = 0;
|
|
|
1c7488 |
if ( !data.found
|
|
|
1c7488 |
|| data.lmin >= -HELI_PRECISION
|
|
|
1c7488 |
|| data.lmax <= HELI_PRECISION )
|
|
|
f80a0a |
{
|
|
|
f80a0a |
info->ax = info->ay = 0;
|
|
|
1c7488 |
return FALSE;
|
|
|
f80a0a |
}
|
|
|
1c7488 |
|
|
|
44355f |
// calc normal
|
|
|
1c7488 |
double l, nx, ny;
|
|
|
f8c1ea |
if (-data.lmin < data.lmax) {
|
|
|
f8c1ea |
l = data.lmin;
|
|
|
1c7488 |
nx = data.nxMin;
|
|
|
1c7488 |
ny = data.nyMin;
|
|
|
1c7488 |
} else {
|
|
|
1c7488 |
l = data.lmax;
|
|
|
1c7488 |
nx = data.nxMax;
|
|
|
1c7488 |
ny = data.nyMax;
|
|
|
1c7488 |
}
|
|
|
1c7488 |
|
|
|
44355f |
// calc displacement
|
|
|
f80a0a |
info->actualCollision = TRUE;
|
|
|
f8c1ea |
double kd = (nx*data.dirx + ny*data.diry)*l;
|
|
|
f80a0a |
info->distance = kd;
|
|
|
f80a0a |
if (kd > distance + HELI_PRECISION) kd -= distance - HELI_PRECISION; else
|
|
|
f80a0a |
if (kd < -distance - HELI_PRECISION) kd += distance - HELI_PRECISION; else
|
|
|
f80a0a |
{ kd = 0; info->actualCollision = FALSE; }
|
|
|
f80a0a |
|
|
|
f80a0a |
if (info->actualCollision) {
|
|
|
f80a0a |
info->dx = nx*kd;
|
|
|
f80a0a |
info->dy = ny*kd;
|
|
|
f80a0a |
}
|
|
|
f8c1ea |
|
|
|
44355f |
// calc velocity
|
|
|
44355f |
double bounciness = fabs(a->bounciness * b->bounciness);
|
|
|
44355f |
double bouncinessThreshold = fabs(a->bouncinessThreshold) + fabs(b->bouncinessThreshold);
|
|
|
44355f |
if (bounciness < HELI_PRECISION) bounciness = 0;
|
|
|
44355f |
|
|
|
f80a0a |
double vb = -info->vx*nx - info->vy*ny;
|
|
|
f80a0a |
double vs = info->vy*nx - info->vx*ny;
|
|
|
44355f |
|
|
|
f80a0a |
if (vb > -0.5*HELI_PRECISION) {
|
|
|
f80a0a |
vb -= bouncinessThreshold;
|
|
|
f80a0a |
if (vb < HELI_PRECISION) vb = 0;
|
|
|
f80a0a |
vb *= bounciness;
|
|
|
44355f |
|
|
|
f80a0a |
if (info->actualCollision || vb > HELI_PRECISION) {
|
|
|
f80a0a |
info->vx = nx*vb - ny*vs;
|
|
|
f80a0a |
info->vy = ny*vb + nx*vs;
|
|
|
f80a0a |
}
|
|
|
f80a0a |
}
|
|
|
44355f |
|
|
|
f80a0a |
// calc weight
|
|
|
f80a0a |
double w = -info->ax*nx - info->ay*ny;
|
|
|
f80a0a |
if (w < HELI_PRECISION) w = 0;
|
|
|
f80a0a |
if (fabs(vb) > 0.4*HELI_PRECISION) w = 0;
|
|
|
f80a0a |
info->ax = nx*w;
|
|
|
f80a0a |
info->ay = ny*w;
|
|
|
1c7488 |
|
|
|
1c7488 |
return TRUE;
|
|
|
8535a3 |
}
|
|
|
8535a3 |
|
|
|
f80a0a |
|
|
|
8535a3 |
int heliPointCollision(HeliCollider *c, double x, double y) {
|
|
|
8535a3 |
x -= c->x;
|
|
|
8535a3 |
y -= c->y;
|
|
|
8535a3 |
if (c->type == COLLIDER_CIRCLE) {
|
|
|
8535a3 |
return x*x + y*y <= c->radius*c->radius;
|
|
|
8535a3 |
} else
|
|
|
8535a3 |
if (c->type == COLLIDER_RECTANGLE) {
|
|
|
8535a3 |
double a = c->rotation*(PI/180);
|
|
|
8535a3 |
double sn = sin(a);
|
|
|
8535a3 |
double cn = cos(a);
|
|
|
8535a3 |
return fabs(x*cn - y*sn) <= c->width*0.5
|
|
|
8535a3 |
&& fabs(x*sn + y*cn) <= c->height*0.5;
|
|
|
8535a3 |
}
|
|
|
8535a3 |
return FALSE;
|
|
|
8535a3 |
}
|
|
|
8535a3 |
|