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(
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