Flash CS4 + ActionScript 3.0으로 게임 수학/물리 구현 공부 계속.

3-3: 가늘고 긴 물체와 원의 충돌판정.

파란 원와 빨간 사선 사각형은 각각 마우스로 드래그가 가능하고 충돌하면 gotoAndStop()을 이용해 아픈 표정으로 바뀐다.

충돌체크는 벡터를 이용해 원의 중심과 사선 사각형의 최단거리를 구한 뒤 원과 사선 사각형 양 끝에 달린 원의 반지름의 합과 비교하여 구한다. 핵심은 원의 중심과 사선 사각형 사이 최단거리를 구하는 문제.
1. 선분상의 점 p = at + b (0<= t <= 1)와 점 (x0, y0)과의 거리를 t의 함수로 나타내고 함수를 t에 대해서 미분하여 거리의 최소값을 제공하는 t를 구한다.
2. 선분을 포함하는 직선 p = at + b에 점 (x0, y0)로부터 수선을 내리고 벡터의 내적을 이용해 거리의 최소값을 제공하는 t를 구한다.

책에서는 1을 이용해 구하고 있다.
선분상의 점과 점(x0, y0)의 거리를 L이라 할 때,
L^2 = (px - x0)^2+(py - y0)^2
이것을 전개하고 두번 미분하여 t를 구하면 위 swf에 써놓은 공식처럼 된다.
t를 (0<= t <= 1)로 범위를 조정하고 구한 t를 선분의 식인 px = axt + b, py=ayt+ b에 대입해서 최단거리의 제곱을 구한 뒤 원들의 반지름과 길이 비교하는 방식.

대충은 성공했는데 기울어진 사각형의 벡터 구하는 법을 몰라 헤멨었다.
사선의 벡터값은 사선을 포함하는 사각형의 넓이값을 벡터의 x값으로, 높이값을 벡터의 y값으로 구하면 되는 간단한 것이었다. 플래시에서 값을 구할 때는 기울어진 사각형 무비클립의 중심점에서 시작되는 line으로 위치와 각도를 잡은 다음에 그 line의 width, height값으로 벡터의 x, y값을 구하면 된다.

14/12/11 목

소스(화면에 미리 'cirBm' 'boxRm'이란 이름의 파란색 원, 빨간색 사선 사각형 무비클립을 만들어두었다.):

var cirB = cirBm; //파란 원
var boxR = boxRm; //빨간 사선 사각형
var hit:Boolean=false;
var dx,dy; //거리 차 저장용 변수

function init(){
	cirB.r = cirB.width/2;//파란 원의 반지름
	boxR.r = 37/2; //빨간 사선 사각형 좌우 끝 원의 반지름
	boxR.vx = 162, boxR.vy = 70; //사선 사각형 벡터값
	cirB.gotoAndStop(1);//원 일반 표정으로
	boxR.gotoAndStop(1);
	
	cirB.addEventListener(MouseEvent.MOUSE_DOWN,mDown);
	cirB.addEventListener(MouseEvent.MOUSE_UP,mUp);
	boxR.addEventListener(MouseEvent.MOUSE_DOWN,mDown);
	boxR.addEventListener(MouseEvent.MOUSE_UP,mUp);
	//checkHitCS(cirB, boxR);
	stage.addEventListener(Event.ENTER_FRAME,loop);
}
function mDown(e:MouseEvent){
	//선택한 원 맨 위로 보내기.
	e.currentTarget.parent.setChildIndex(e.currentTarget, 
				e.currentTarget.parent.numChildren-1);
	e.currentTarget.startDrag();//원 마우스 따라다니기
}
function mUp(e:MouseEvent){
	e.currentTarget.stopDrag();//원 마우스 따라다니기 중지
}
function loop(e:Event){
	checkHitCS(cirB, boxR);//원과 박스간 충돌체크
	if(hit){ //두 원 충돌시 처리
		cirB.gotoAndStop(2);//원 놀란 표정으로
		boxR.gotoAndStop(2);
	}else{
		cirB.gotoAndStop(1);//원 일반 표정으로
		boxR.gotoAndStop(1);
	}
}
function checkHitCS(aCir, bBoxc){
	hit = false; //충돌은 일단 false로.
	var t; //선분과 점의 최단거리 제공하는 값 저장용
	var mx, my; //최소위치 좌표 저장용
	var ar; //원과 사선 사각형 반지름 합 저장용
	var fDistSqr; //거리 제곱 저장용
	
	dx = aCir.x - bBoxc.x;
	dy = aCir.y - bBoxc.y;
	t = (bBoxc.vx * dx + bBoxc.vy * dy) / 
		(bBoxc.vx * bBoxc.vx + bBoxc.vy * bBoxc.vy);
		
	if(t < 0) t = 0; //t의 하한
	if(t > 1) t = 1; //t의 상한
	
	mx = bBoxc.vx * t + bBoxc.x; //최소위치가 되는 좌표
	my = bBoxc.vy * t + bBoxc.y;
	
	fDistSqr = (mx - aCir.x)*(mx - aCir.x) + 
				(my - aCir.y)*(my - aCir.y); //거리의 제곱
	ar = aCir.r + bBoxc.r;
	if(fDistSqr < ar * ar){
		hit= true;
	}
}
init();

 


3-4: 부채꼴 물체와 원의 충돌판정.

파란 원와 노란 부채꼴은 각각 마우스로 드래그가 가능하고 충돌하면 gotoAndStop()을 이용해 아픈 표정으로 바뀐다.

충돌체크는 크게 3 단계로 나뉜다.
1. 부채꼴의 중심점이 원의 내부에 있으면 충돌.
2. 원의 중심점이 부채꼴 내부에 있으면 충돌.
2'. 원의 중심점이 부채꼴을 형성하는 두 벡터 사이에 있고, 또 원의 중심점과 부채꼴 중심점 사이 거리가 원의 반지름 + 부채꼴 반지름 보다 작으면 충돌.

3. 부채꼴을 형성하는 두 벡터에의해 정해지는, 부채꼴 외주를 형성하는 두 선분 중 하나와 원이 교점을 가지면 충돌.

소스(화면에 미리 'cirBm' 'fanYm'이란 이름의 파란색 원과 노란색 부채꼴 무비클립을 만들어두었다.):

var cirB = cirBm; //파란 원
var fanY = fanYm; //노란 부채꼴
var hit:Boolean=false;

function init(){
	cirB.r = cirB.width/2;//파란 원의 반지름
	fanY.r = 100; //노란 부채꼴 원의 반지름
	fanY.vx1 = -78, fanY.vy1 = -63; //부채꼴 좌측 사선 벡터값
	fanY.vx2 = 76, fanY.vy2 = -65; //부채꼴 우측 사선 벡터값
	//아래는 다양한 방법으로 부채꼴 사선 벡터값 구하는 방법들
	//fanY.ang1 = -PI /4; //45도
	//fanY.ang2 = -PI /1.5; //120도
	//fanY.ang1 = Math.atan2(-63, -78); //부채꼴 좌측 사선 각도
	//fanY.ang2 = Math.atan2(-65, 76); //부채꼴 우측 사선 각도
	//부채꼴 사선들 벡터값
	//fanY.vx1 = fanY.r * Math.cos(fanY.ang1);
	//fanY.vy1 = fanY.r * Math.sin(fanY.ang1);
	//fanY.vx2 = fanY.r * Math.cos(fanY.ang2);
	//fanY.vy2 = fanY.r * Math.sin(fanY.ang2);
	
	cirB.gotoAndStop(1);//원 일반 표정으로
	fanY.gotoAndStop(1);
	
	cirB.addEventListener(MouseEvent.MOUSE_DOWN,mDown);
	cirB.addEventListener(MouseEvent.MOUSE_UP,mUp);
	fanY.addEventListener(MouseEvent.MOUSE_DOWN,mDown);
	fanY.addEventListener(MouseEvent.MOUSE_UP,mUp);
	stage.addEventListener(Event.ENTER_FRAME,loop);
}
function mDown(e:MouseEvent){
	//선택한 원 맨 위로 보내기.
	e.currentTarget.parent.setChildIndex(e.currentTarget, 
				e.currentTarget.parent.numChildren-1);
	e.currentTarget.startDrag();//원 마우스 따라다니기
}
function mUp(e:MouseEvent){
	e.currentTarget.stopDrag();//원 마우스 따라다니기 중지
}
function loop(e:Event){
	checkHitCF(cirB, fanY);//원과 부채꼴 충돌체크
	if(hit){ //두 원 충돌시 처리
		cirB.gotoAndStop(2);//원 놀란 표정으로
		fanY.gotoAndStop(2);
	}else{
		cirB.gotoAndStop(1);//원 일반 표정으로
		fanY.gotoAndStop(1);
	}
}
function checkHitCF(aCir, bFan){ //원과 부채꼴 충돌체크
	hit = false; //충돌은 일단 false로.
	var dx, dy; //위치의 차이
	var fAlpha, fBeta, fDelta;
	var ar; //두 반지름 더한 것
	var fDistSqr; //거리 제곱 저장용
	var a,b,c,d;
	var t;
	dx = aCir.x - bFan.x;
	dy = aCir.y - bFan.y;
	fDistSqr = dx * dx + dy * dy;
	//부채꼴의 중심이 원의 내부에 있을 경우
	if(fDistSqr < aCir.r * aCir.r){
		hit = true;
		//trace("inner");
	}else{
		//원 중심이 부채꼴을 형성한 두 벡터 사이에 있을 경우
		fDelta = bFan.vx1 * bFan.vy2 - bFan.vx2 * bFan.vy1;
		fAlpha = (dx * bFan.vy2 - dy * bFan.vx2)/fDelta;
		fBeta = (-dx * bFan.vy1 + dy * bFan.vx1)/fDelta;
		if((fAlpha >= 0)&&(fBeta >= 0)){
			ar = bFan.r + aCir.r;
			if(fDistSqr <= ar * ar){
				hit = true;
				//trace("between");
			}
		}else{
		//부채꼴 두 벡터의 두 선분 중 하나와 교점을 갖는 경우
			a = bFan.vx1 * bFan.vx1 + bFan.vy1 * bFan.vy1;
			b = -(bFan.vx1 * dx + bFan.vy1 * dy);
			c = dx * dx + dy * dy - aCir.r * aCir.r;
			d = b * b - a * c;
			if(d >= 0){
				t = (-b - Math.sqrt(d))/a;
				if((t >= 0)&&(t <= 1)){
					hit = true;
					//trace("left");
				}
			}
			a = bFan.vx2 * bFan.vx2 + bFan.vy2 * bFan.vy2;
			b = -(bFan.vx2 * dx + bFan.vy2 * dy);
			c = dx * dx + dy * dy - aCir.r * aCir.r;
			d = b * b - a * c;
			if(d >= 0){
				t = (-b - Math.sqrt(d))/a;
				if((t >= 0)&&(t <= 1)){
					hit = true;
					//trace("right");
				}
			}
		}
	}
}
init();