首页 > 代码库 > 双圆碰撞简析

双圆碰撞简析

 之前在SSZX上课时写老程序Star,准备加上碰撞系统.碰撞后的速度改变比较简单,正交分解即可,但碰撞检测却卡了一段时间

双圆碰撞检测,最简单的碰撞检测.它的确很简单,至少与其他碰撞检测相比,有着更易于思考的算法,更少的参数.在一个月前开始思考怎么实现时,我的构思是这样:

struct Ball{double x;double y;//表示圆所处的位置,(x,y).double xm;double ym;//xm,ym表示每次移动的量,即每一帧将其X方向移动xm,Y方向移动ym.double r;//圆的半径}ball[MAXNUM];inline double distant_sum(const ball &a,const ball &b){ return sqrt(pow(a.x-b.x,2)+pow(a.y-b.y,2));}//返回两距离>//存储一个圆的结构体
double x;double y;//表示圆所处的位置,(x,y).double xm;double ym;//xm,ym表示每次移动的量,即每一帧将其X方向移动xm,Y方向移动ym.double r;//圆的半径}ball[MAXNUM];inline double distant_sum(const ball &a,const ball &b){ return sqrt(pow(a.x-b.x,2)+pow(a.y-b.y,2));}//返回两距离
然后这样:

if(distant_sum(ball[i],ball[j])<ball[i].r+ball[j].r)
  {/*other process*/}

也就是说,两圆距离小于半径和时,即判定为碰撞.这个方法非常显然,非常简单,但显然,它在两圆的速度很高时,是很不可靠的:技术分享

虽然一个圆运动速度如此之快的情况是很少的,在高速下撞击另一个圆的几率也不大,但毕竟有BUG就要修.

查阅了一些关于碰撞检测的资料后,在某东方同人的检测代码中发现了一个有趣的方法:每次移动(有选择性的)判断两圆碰撞时,将圆用循环暴力慢慢移动,每次移动较小的圆的半径长度(或是一更小的时间单位),暴力检测每次移动后是否碰撞.

这种方法能解决大部分的BUG,但显然也只是"大部分":

技术分享



而且可以想象如果优化不好,这种方法在球很多是得有多卡........代码就不上了,敲了半天没调好.

最后我放弃了投机取巧,选择了更暴力的方法,就是直接算碰撞点.

不考虑两圆的速度,而考虑相对另一圆的速度,即假设一个不动,另一个的速度为两圆的相对速度,这样,可以将情况看作一个圆在一帧内沿一段一次函数运动,另一个不动,检测而这是否碰撞

事实上,这个问题等价于在一段一次函数上是否存在一个点(动圆圆心)到另一点(定圆圆心)的距离小于等于一个常量(两圆半径和),如果有,返回距离开始点最近的那个点(显然大部分碰撞的情况会有两个点到另一圆圆心距离等于两圆半径和)

这就非常简单了,稍微推一下公式就行了:

技术分享

((y1 - y2)/(x1 - x2)*x + y1 - (y1 - y2)/(x1 - x2)*x1 - y3)^2 + (x - x3)^2 - (r + R)^2=0

(图中忘写=0)

然后就用求根公式:

x == (-2 x1^2 x3 + 4 x1 x2 x3 - 2 x2^2 x3 - 2 x2 y1^2 + 2 x1 y1 y2 +
     2 x2 y1 y2 - 2 x1 y2^2 - 2 x1 y1 y3 + 2 x2 y1 y3 + 2 x1 y2 y3 -
     2 x2 y2 y3 - \[Sqrt]((2 x1^2 x3 - 4 x1 x2 x3 + 2 x2^2 x3 +
          2 x2 y1^2 - 2 x1 y1 y2 - 2 x2 y1 y2 + 2 x1 y2^2 +
          2 x1 y1 y3 - 2 x2 y1 y3 - 2 x1 y2 y3 + 2 x2 y2 y3)^2 -
        4 (-x1^2 + 2 x1 x2 - x2^2 - y1^2 + 2 y1 y2 -
           y2^2) (r^2 x1^2 + 2 r R x1^2 + R^2 x1^2 - 2 r^2 x1 x2 -
           4 r R x1 x2 - 2 R^2 x1 x2 + r^2 x2^2 + 2 r R x2^2 +
           R^2 x2^2 - x1^2 x3^2 + 2 x1 x2 x3^2 - x2^2 x3^2 -
           x2^2 y1^2 + 2 x1 x2 y1 y2 - x1^2 y2^2 - 2 x1 x2 y1 y3 +
           2 x2^2 y1 y3 + 2 x1^2 y2 y3 - 2 x1 x2 y2 y3 - x1^2 y3^2 +
           2 x1 x2 y3^2 - x2^2 y3^2)))/(2 (-x1^2 + 2 x1 x2 - x2^2 -
       y1^2 + 2 y1 y2 - y2^2))

||

 x == (-2 x1^2 x3 + 4 x1 x2 x3 - 2 x2^2 x3 - 2 x2 y1^2 + 2 x1 y1 y2 +
     2 x2 y1 y2 - 2 x1 y2^2 - 2 x1 y1 y3 + 2 x2 y1 y3 + 2 x1 y2 y3 -
     2 x2 y2 y3 + \[Sqrt]((2 x1^2 x3 - 4 x1 x2 x3 + 2 x2^2 x3 +
          2 x2 y1^2 - 2 x1 y1 y2 - 2 x2 y1 y2 + 2 x1 y2^2 +
          2 x1 y1 y3 - 2 x2 y1 y3 - 2 x1 y2 y3 + 2 x2 y2 y3)^2 -
        4 (-x1^2 + 2 x1 x2 - x2^2 - y1^2 + 2 y1 y2 -
           y2^2) (r^2 x1^2 + 2 r R x1^2 + R^2 x1^2 - 2 r^2 x1 x2 -
           4 r R x1 x2 - 2 R^2 x1 x2 + r^2 x2^2 + 2 r R x2^2 +
           R^2 x2^2 - x1^2 x3^2 + 2 x1 x2 x3^2 - x2^2 x3^2 -
           x2^2 y1^2 + 2 x1 x2 y1 y2 - x1^2 y2^2 - 2 x1 x2 y1 y3 +
           2 x2^2 y1 y3 + 2 x1^2 y2 y3 - 2 x1 x2 y2 y3 - x1^2 y3^2 +
           2 x1 x2 y3^2 - x2^2 y3^2)))/(2 (-x1^2 + 2 x1 x2 - x2^2 -
       y1^2 + 2 y1 y2 - y2^2))


判别式为:

-(1/((x1 - x2)^2))
 4 (-r^2 x1^2 - 2 r R x1^2 - R^2 x1^2 + 2 r^2 x1 x2 + 4 r R x1 x2 +
    2 R^2 x1 x2 - r^2 x2^2 - 2 r R x2^2 - R^2 x2^2 - r^2 y1^2 -
    2 r R y1^2 - R^2 y1^2 + x2^2 y1^2 - 2 x2 x3 y1^2 + x3^2 y1^2 +
    2 r^2 y1 y2 + 4 r R y1 y2 + 2 R^2 y1 y2 - 2 x1 x2 y1 y2 +
    2 x1 x3 y1 y2 + 2 x2 x3 y1 y2 - 2 x3^2 y1 y2 - r^2 y2^2 -
    2 r R y2^2 - R^2 y2^2 + x1^2 y2^2 - 2 x1 x3 y2^2 + x3^2 y2^2 +
    2 x1 x2 y1 y3 - 2 x2^2 y1 y3 - 2 x1 x3 y1 y3 + 2 x2 x3 y1 y3 -
    2 x1^2 y2 y3 + 2 x1 x2 y2 y3 + 2 x1 x3 y2 y3 - 2 x2 x3 y2 y3 +
    x1^2 y3^2 - 2 x1 x2 y3^2 + x2^2 y3^2)

好了,为题解决,写了个小程序验证:

#include<cmath>
 #include<windows.h>
 #include<iostream>
 #include<cstdio>
 #include<cstdlib>
 #include<cstring>

 #include<algorithm>
 #include<vector>
 #include<iostream>
 #include<ctime>
 #include<process.h>
#include<vector>
/*DCY11011 2016*/

 using namespace std;
 HANDLE h=CreateFile(TEXT("CONOUT$"),GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL);

vector <double> xy,c1;

struct Ball{
	double x;
	double y;
	double xm;
	double ym;
	double r;
};

Ball ball[3];

 bool Circle(HDC hdc,int x,int y,int r){
 	return Ellipse(hdc,x-r,y-r,x+r,y+r);
 }
  
 HPEN hpen;
 HBRUSH hbrush;
 HWND hwnd=0;
 HDC dc,hmem;

int setfillcolor(COLORREF c){
	DeleteObject(hbrush);
	hbrush=CreateSolidBrush(c);
	SelectObject(hmem,hbrush);
	return 0;
}
int setcolor(COLORREF c,int a=1){
	DeleteObject(hpen);
	hpen=CreatePen(PS_SOLID,a,c);
	SelectObject(hmem,hpen);
	return 0;
}
int in(int a,int b,int c,int d,int x,int y)
{
	return x>a&&x<c&&y>b&&y<d;
}
void line(HDC hdc,int a,int b,int c,int d)
{
     MoveToEx (hdc,a,b,NULL) ;
     LineTo   (hdc,c,d) ;
}
int bar(HDC hdc,int x,int y,int x1,int y1){
	RECT rc={x,y,x1,y1};
	FillRect(hdc,&rc,hbrush);
	return 0;
}
int ctx,cty;
double xc1,yc1,xc2,yc2;
RECT rc;

bool ea=0;
void Worker(LPVOID lpParam){
 	const volatile bool *run=(bool *)lpParam;
 	while(*run){
 		
 		GetClientRect(FindWindow(TEXT("ConsoleWindowClass"),TEXT("Star C")),&rc);
		ctx=rc.right;cty=rc.bottom;
		ea=1;
		for(int i=0;i<3;i++){
			//碰撞检测
			{   
				for(int j=i+1;j<3;j++){
					double x2=ball[i].xm-ball[j].xm;
					double y2=ball[i].ym-ball[j].ym;
					double x1=ball[i].x;
					double y1=ball[i].y;
					double x3=ball[j].x;
					double y3=ball[j].y;
					double L=ball[i].r+ball[j].r;
					
					double t=sqrt(- pow((x1-x2),2)* (-L*L* (x1*x1 - 2*x1*x2 + x2*x2 + pow((y1-y2),2)) +pow((x2*y1-x3*y1-x1*y2+x3*y2+x1*y3-x2*y3),2)));
					if(t<0)continue; 
  					xc1 = (1/(
 	 				pow((x1-x2),2)+ pow((y1-y2),2)))*(x1*x1*x3 - 2*x1*x2*x3 + x2*x2*x3 + x2*y1*y1 - 
				    x1*y1*y2 - x2*y1*y2 + x1*y2*y2 + x1*y1*y3 - x2*y1*y3 - x1*y2*y3 + 
				    x2*y2*y3 + 
				    t);
				    yc1 = (y1 - y2)/(x1 - x2)*xc1 + y1 - (y1 - y2)/(x1 - x2)*x1;
				    xc2 = (1/(
 	 				pow((x1-x2),2)+ pow((y1-y2),2)))*(x1*x1*x3 - 2*x1*x2*x3 + x2*x2*x3 + x2*y1*y1 - 
				    x1*y1*y2 - x2*y1*y2 + x1*y2*y2 + x1*y1*y3 - x2*y1*y3 - x1*y2*y3 + 
				    x2*y2*y3 - 
				    t);
				    yc2 = (y1 - y2)/(x1 - x2)*xc2 + y1 - (y1 - y2)/(x1 - x2)*x1;
				    
				    if(((xc1>=x1&&xc1<=x2+x1)||(xc1>=x2+x1&&xc1<=x1))&&((yc1>=y1&&yc1<=y2+y1)||(yc1>=y2+y1&&yc1<=y1)))
				    {
				    	c1.push_back(xc1);c1.push_back(yc1);
				    }
				    if(((xc2>=x1&&xc2<=x2+x1)||(xc2>=x2+x1&&xc2<=x1))&&((yc2>=y1&&yc2<=y2+y1)||(yc2>=y2+y1&&yc2<=y1)))
				    {
				    	c1.push_back(xc1);c1.push_back(yc1);
				    }
				    if(abs(x1-xc1)<abs(x1-xc2))
				    {
				    	 xy.push_back(xc1);
				    	 xy.push_back(yc1);
				    	 xy.push_back(i);
				    	 xy.push_back(j);
				    }
				   
				    else{
					    	
					    xy.push_back(xc2);
					    xy.push_back(yc2);
					    xy.push_back(i);
				    	xy.push_back(j);
				    }
         			//system("cls") ;			
				}
			}
			
			ea=0;
			ball[i].x+=ball[i].xm;
			ball[i].y+=ball[i].ym;
			
			if(ball[i].x>600||ball[i].x<0)ball[i].xm=-ball[i].xm;
			if(ball[i].y<0||ball[i].y>500)ball[i].ym=-ball[i].ym;
			
		}
		
		
 		Sleep(100);
 	}
 	_endthread();
 }
 void Messageer(LPVOID lpParam){
 	const volatile bool *run=(bool *)lpParam;
 	while(*run){
 		
 		while(!GetAsyncKeyState(VK_LBUTTON )&&0x8000);
 		while(GetAsyncKeyState(VK_LBUTTON )&&0x8000){
 			//cout<<"LBUTTON_DOWN"<<endl;
 			Sleep(10);
 		}
 	}
 	_endthread();
 }
 
 void ConsoloPrinter(LPVOID lpParam){
 	const volatile bool *run=(bool *)lpParam;
 	dc=GetDC(hwnd);hmem=CreateCompatibleDC(dc);
 	HBITMAP hbit=CreateCompatibleBitmap(dc,1000,700);
 	ReleaseDC(hwnd,dc);
 	SelectObject(hmem,hbit);
 	SetBkMode(hmem,TRANSPARENT);
	 while(*run){
 	  
 	  setfillcolor(0x000000);
 	  bar(hmem,0,0,ctx,cty);
 	  
 	  SetTextColor(hmem,0xddffdd);
 	  char temp[40];
 	  sprintf(temp,"Test Text");
 	  TextOut(hmem,100,100,temp,strlen(temp));
 	  
 	  for(int i=0;i<3;i++){
 	  	setcolor(RGB(255,0,0),1);
 	  	Circle(hmem,ball[i].x,ball[i].y,ball[i].r);
 	  }
 	  while(ea); 
 	  
 	  while(!xy.empty()){
 	  	
 	  	double x,y,x1,y1;
 	  	y1=xy.back();xy.pop_back();
 	  	x1=xy.back();xy.pop_back(); 
		y=xy.back();xy.pop_back();
 	  	x=xy.back();xy.pop_back();
 	  	sprintf(temp,"%lf-%lf",&x1,&y1);
 	    TextOut(hmem,x,y,temp,strlen(temp));
 	   
 	    setfillcolor(0xffff00);
 	    Circle(hmem,x,y,5);
 	  }
 	  while(!c1.empty()){
 	  	double x,y;
 	  	y=c1.back();c1.pop_back();
		x=c1.back();c1.pop_back();
		setfillcolor(RGB(255,0,0));
		Circle(hmem,x,y,10) ;
 	  }
 	  
 	  
 	  
 	  HDC hdc=GetDC(hwnd);
 	  BitBlt(hdc,0,96,1000,700,hmem,0,0,SRCCOPY);
 	  ReleaseDC(hwnd,hdc);
	  DeleteObject(hbrush);
	  DeleteObject(hpen);
 	  Sleep(1000/60);
	 }
 	DeleteObject(hpen);
 	DeleteObject(hbrush);
 	_endthread();
 }
 int main(){
 	
 	ball[0]={0,0,4,3,30};
 	ball[1]={300,300,5,-5,40};
 	ball[2]={100,100,2,2,50};
 	SetConsoleTitle("Star C");
 	Sleep(500);
	hwnd=FindWindow(0,"Star C");
	MoveWindow(hwnd,100,0,1000,700,1);
	COORD cd={120,50};
	SetConsoleScreenBufferSize(h,cd);
	CloseHandle(h);
	bool run=true;
	_beginthread(ConsoloPrinter,0,&run);
	_beginthread(Worker,0,&run);
	_beginthread(Messageer,0,&run);
	while(run){
		system("cls");
		cout<<"Input...->";
		string t;
		cin>>t;
		if(t=="Commad"){
			//function 
			continue;
		}
		cout<<"No such instruction!(input 'help' for help!)"<<endl;
		getchar();getchar();
	}
	
}



双圆碰撞简析