Blame src/collider.c

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(
1c7488
	HeliCollider *a, HeliCollider *b,
1c7488
	double *dx, double *dy,
1c7488
	double *vx, double *vy,
1c7488
	double bounciness )
1c7488
{
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) {
1c7488
		data.radius = fabs(a->radius) + fabs(b->radius);
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);
1c7488
		data.radius = fabs(a->radius) + fabs(b->radius);
1c7488
		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;
1c7488
		data.radius = fabs(a->radius) + fabs(b->radius);
1c7488
		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);
1c7488
		data.radius = fabs(a->radius) + fabs(b->radius);
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?
1c7488
	*dx = 0; *dy = 0;
1c7488
	if ( !data.found
1c7488
	  || data.lmin >= -HELI_PRECISION
1c7488
	  || data.lmax <= HELI_PRECISION )
1c7488
		return FALSE;
1c7488
	
1c7488
	// calc displacement and velocity
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
	
f8c1ea
	double kd = (nx*data.dirx + ny*data.diry)*l;
f8c1ea
	*dx = nx*kd;
f8c1ea
	*dy = ny*kd;
f8c1ea
	
1c7488
	double kn = *vx*nx + *vy*ny;
1c7488
	if (kn < 0) {
1c7488
		*vx -= 2*kn*fabs(bounciness)*nx;
1c7488
		*vy -= 2*kn*fabs(bounciness)*ny;
1c7488
	}
1c7488
	
1c7488
	return TRUE;
8535a3
}
8535a3
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