Blob Blame Raw

#include "private.h"


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_SQR) {
			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 =  0.5*cn*fabs(c->width);
	double wy =  0.5*sn*fabs(c->width);
	double hx = -0.5*sn*fabs(c->height);
	double hy =  0.5*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[2*i+0] - x;
		data->y1 = corners[2*i+1] - y;
		data->x2 = corners[2*j+0] - x;
		data->y2 = corners[2*j+1] - y;
		collision(data);
		data->x2 = data->x1;
		data->y2 = data->y1;
		collision(data);
	}
}


int heliCheckCollision(
	HeliCollider *a,
	HeliCollider *b,
	HeliCollisionInfo *info,
	double distance )
{
	distance = fabs(distance);
	HeliCollisionData data = {};
	
	// calc "moving" direction
	data.dirx = b->x - a->x;
	data.diry = b->y - a->y;
	double 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) + distance;
		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) + distance;
		if (data.radius > HELI_PRECISION) 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) + distance;
		if (data.radius > HELI_PRECISION) 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) + distance;
		for(int i = 0; i < 4; ++i)
			collisionCorners(&data, cornersB, cornersA[2*i+0], cornersA[2*i+1]);
		data.flip = TRUE;
		for(int i = 0; i < 4; ++i)
			collisionCorners(&data, cornersA, cornersB[2*i+0], cornersB[2*i+1]);
	}
	
	// is collision found?
	info->actualCollision = FALSE;
	info->distance = 0;
	info->dx = info->dy = 0;
	if ( !data.found
	  || data.lmin >= -HELI_PRECISION
	  || data.lmax <= HELI_PRECISION )
	{
		info->ax = info->ay = 0;
		return FALSE;
	}
	
	// calc normal
	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;
	}
	
	// calc displacement
	info->actualCollision = TRUE;
	double kd = (nx*data.dirx + ny*data.diry)*l;
	info->distance = kd;
	if (kd >  distance + HELI_PRECISION) kd -= distance - HELI_PRECISION; else
	if (kd < -distance - HELI_PRECISION) kd += distance - HELI_PRECISION; else
		{ kd = 0; info->actualCollision = FALSE; }
	
	if (info->actualCollision) {
		info->dx = nx*kd;
		info->dy = ny*kd;
	}
	
	// calc velocity
	double bounciness = fabs(a->bounciness * b->bounciness);
	double bouncinessThreshold = fabs(a->bouncinessThreshold) + fabs(b->bouncinessThreshold);
	if (bounciness < HELI_PRECISION) bounciness = 0;
	
	double vb = -info->vx*nx - info->vy*ny;
	double vs =  info->vy*nx - info->vx*ny;
	
	if (vb > -0.5*HELI_PRECISION) {
		vb -= bouncinessThreshold;
		if (vb < HELI_PRECISION) vb = 0;
		vb *= bounciness;
	
		if (info->actualCollision || vb > HELI_PRECISION) {
			info->vx = nx*vb - ny*vs;
			info->vy = ny*vb + nx*vs;
		}
	}
	
	// calc weight
	double w = -info->ax*nx - info->ay*ny;
	if (w < HELI_PRECISION) w = 0;
	if (fabs(vb) > 0.4*HELI_PRECISION) w = 0;
	info->ax = nx*w;
	info->ay = ny*w;
	
	return TRUE;
}


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;
}