오늘을 붙들어라. 되도록 이면 내일에 의지하지 말라. 그 날 그 날이 일 년 중에서 최선의 날이다. - 에머슨
* 원저자 : Jeff Lander (gamasutra)
시작하며 #
지난 몇 년동안은 믿을 수 없는 속도로 (기술이) 진화되어왔다. 게임상의 비쥬얼한 품질상의 놀라운 개선은 단지 일년내에 이루어진 일이다. 3D 하드웨어가 업계에 주요 이슈가 될 것이라는 예측은 현실이 되고 있다.
소비자들은 몇 년전 만해도 수천불을 지불해야 구입가능한, 지금 3D 그래픽 성능을 가지고 있는 카드를 100달러이내의 가격으로 살 수 있다. 이 것은 또한 게임 개발자들에게는 다른 컴퓨터 시뮬레이션 분야를 실현하는데 전담적인 프로세싱 타임을 얻을 수 있게 해주었다.
간단한 트릭과 기술이 그렇게 수많은 효과와 어플리케이션의 문을 열 수 있다는 것은 나를 계속 놀라게 한다. 이 아티클에서는 나는 충돌체크 문제에 대해서만 조사해볼 예정이다. 그래픽스 시뮬레이션에서 충돌체크는 큰 이슈이다. 사실, 이것은 실제 연구분야이기 때문에 SIGGRAPH 및 전문 저널에서 막대한 양의 정보를 구할 수 있다.
일단은 다양한 게임 어플리케이션에서 중요하게 여겨질 수 있는 몇가지 일반적인 문제들에서부터 시작해보도록 하자. 꽤 간단해보이는 이 루틴들 은 여러분의 라이브러리에 손쉽게 추가할 수 있는 것들이다. 첫번째 이슈는 임의의 영역내에 한 정점이 어디에 존재하는지 알아내는 방법을 알아내는 것이다. 볼록 폴리곤내의 정점의 위치를 찾아내는 것은 매우 쉽다.

그림 1은 간단한 사면체 폴리곤내의 정점을 보여준다. 우리의 첫번째 단계는 각 폴리곤 면을 의미하는 벡터를 생성하고 테스트 포인트부터 각 면의 첫번째 정점까지의 벡터를 생성한다. 알다시피 두 벡터의 내적(이게 먼지 기억이 안나면 클릭.
)을 구하면 두 벡터 간의 각에 대한 cos() 값을 구할 수 있다. 만약 각 면에 대한 내적값이 양수라면, 모든 각은 90보다 작고 정점은 폴리곤 내부에 있다는 뜻이 된다.
)을 구하면 두 벡터 간의 각에 대한 cos() 값을 구할 수 있다. 만약 각 면에 대한 내적값이 양수라면, 모든 각은 90보다 작고 정점은 폴리곤 내부에 있다는 뜻이 된다.
이 규칙은 몇가지 면에서 매우 유용하다. 어쨌거나 이것은 경계가 볼록 다각형 형태일 경우에만 동작한다. 우리가 관심있어하는 많은 대부분의 공간들은 실제로는 오목하다 (그림 2를 참조)

이 폴리곤은 듀크 너켐의 레벨내의 캐릭터처럼 생겼다. 그리고 사실, 듀크너켐은 이런 종류의 테스트를 위해서는 매우 괜찮은 어플리케이션이다. 이 게임의 레벨상의 각 "섹터"는 미리정의된 층과 천장의 높이를 가지고 있는 영역을 정의하고 있는, 폴리곤으로 이루어진 경계를 의미한다. 특정 섹터의 안에 있는지 밖에 있는지 여부를 아는 것은 중요한 정보중 하나다. 불행하게도, 앞서서 설명한 내적 테스트 방법은 이런 오목 폴리곤에서는 실행할 수 없다. 이 오목 영역을 더 작은 볼록 다각형들로 쪼개어 생각할 수도 있으나, 이것을 그렇게 효율적이지 않을 수 있다. 운이 좋게도, 이 문제는 컴퓨터 처리에 관련된 기하학 서적에서 흔하게 설명된 "다각형내의 정점"이라는 고전적인 테스트라고 볼 수 있다. 이 문제를 해결하기 위한 많은 접근법이 존재하지만, 여기서는 이들중 두가지만 보도록 하자.
자, 정점들 주위를 돌아보자! #
오목 다각형내에 있는지 여부를 알아내는 한가지 방법에 대한 아이디어는 원은 360도라는 점에서 착안된 것이다. 다각형의 각 정점들과 테스트 정점간의 각을 계산하고, 그 각들을 모두 더한다. 만약 총합이 360이면 다각형 내부에 있다는 의미가 된다. 그림 3을 보면 더욱 이해하기 쉬울 것이다.

이것은 정말로 잘 실행되지만 어쨌거나 그다지 효율적이지 않다. 각 정점까지의 각을 구하는 것은 내적과 arc-코사인 계산을 필요로 하기 때문이다.

좀더 나은 전략은 그림 4에서와 같이 폴리곤을 테스트 정점을 중심으로 사분면으로 분할하는 것이다. 다각형의 첫번째 정점부터 계산을 시작하고 카운터는 0으로 설정한다. 언제라도 면이 한 사분면의 경계에 걸쳐져있다면, 시계방향으로 걸쳐져있는 경우에는 카운터에 1 더하고, 시계 반대방향으로 걸쳐져 있다면 카운터를 1 감소시킨다. 만일 선분이 두개의 사분면을 비스듬하게 가로질러 가고 있다면, 테스트 포인트의 어떤 면이 걸쳐졌는지를 알아야 한다. 그리고 카운터에서 2를 더하거나 빼준다. 그림 4를 보면서 스스로 한번 계산해봐라. 1번 정점에서 시작해보자. 3과 4번 정점사이에 해당하는 선분은 사분면 I와 II에 걸쳐있으므로 1 더해준다. 그리고 4-5 선분은 1을 빼주어야 할 것이다. 이와같은 방법을 계속하면 마지막 면인 11-1까지 도달했다면 아마도 카운터 값으로 확실하게 4가 나올 것이다. 이 알고리즘을 사용한 후에 카운터가 4 혹은 -4와 같다면 테스트 포인트는 다각형 내부에 있다는 의미가 된다. 첨부자료 1에서 이 알고리즘에 대한 코드를 볼 수 있다.
저 선분을 건너지 말 것(Don't Cross that Line) #
사분면을 이용한 방법은 매우 효율적이다. 어쨌거나, 여기에 완전히 다른 접근법이 하나 있다. 이 방법의 흥미로운 점은 테스트 정점으로 부터 명백하게 다각형의 외부에 존재하는 정점으로 선을 그리면 테스트 정점의 외부/내부 여부를 알 수 있다는 점이다. 이제 선에 교차된 다각형 선분이 몇개나 되는지 세어보자. 이 개수가 홀수이면 테스트 정점은 폴리곤 내부에 있다. 만일 이 정점이 짝수면 테스트 정점은 외부에 존재한다. 한번 시도해봐라.
Graphic Gems IV에서 이 방법을 구현한 매우 빠른 방법을 본 적이 있다. This method projects a line from the hit position along the x axis. Only testing line segments that are on either side of this position lets you avoid some calculations. Segments that could cross this line require an x intercept calculation to be sure. However, this can be simplified to eliminate a divide because of the unique needs of the test. 이 루틴을 위한 코드는 "첨부자료 2"에 있다. 이 메소드가 사분면 방식보다 더 빠른지 여부는 전적으로 테스팅되는 폴리곤에 달려있다. 만일 속도가 중요하다면 코드가 간단하므로 여러분의 어플리케이션 상에서 두가지 방법 모두를 구현해보고 비교해봐라.
Standing at Arm's Length #
The above routines are enough to let your player navigate around in a Doom-style level. You would just need to make sure that the player is always inside a sector. If the player leaves one sector and does not enter any other, a "collision" has happened. This works very well. However, using the inside polygon test for collision by itself has a drawback. The player can get very close to the wall of a sector and still be considered "inside." Logically, this works fine. However, in a 3D rendered game engine, being too close to a wall is a bad thing. Textures will look blocky, they can distort badly, and walls may clip out.
What you really want to do is keep the walls at "arm’s length" from the player. You can simply make the logical collision walls closer in than the visual walls; however, this can lead to other problems. So how do I keep the character away from the wall? Turn once again to our dear old friend, the dot product. Take a look at 그림 5.

What I want to know is, how far away is the test point, t, from line segment A. An easy solution would be to find the nearest point, n, to the test point on the line segment and measure the distance to it. First, I create a vector, B, from the test point, t, to vertex p1. I can dot this vector with the line segment A. This will give me the cosine of the interior angle. If this angle is 90 degrees or greater, the nearest point is the vertex itself and I’m done. But let’s say that the dot product gives me 0.7, or the cosine of about 45 degrees. I will then do the same thing on the other side. I create a vector C and dot it with the segment A. If it had returned an angle greater than or equal to 90 degrees, point p2 would be the closest and I would be done again. In this case, the dot product returns 0.75, or the cosine of about 40 degrees. Now that I have the two dot products, a linear ratio will solve the problem.

You can see the code that determines the nearest point on a line segment to an input point in Listing 3. The squared distance from t to n can be used to make sure the player cannot get too close to the wall. When I combine this with the inside-polygon tests, I have the pieces I need to create a Doom-style collision model. In these days of Quake III and Unreal, it may seem a bit retro to talk about Doom-style collision detection. However, the ability to build simple collision boundaries that you can use and modify in real-time is a very attractive feature. Rules in game development are meant to be broken. Just because these days you are displaying a world made of 3D polygons, your collision boundaries don’t have to be 3D polygons. Many of the environments we wish to interact with have boundaries that can easily be defined as 2D concave-polygonal-line-segments. Sometimes the best results can be achieved with simple solutions. If you didn’t have these routines already in your math library, add them and you will be surprised by how often you use them.
3차원 공간상에서 충돌 #
Running around a maze is one thing, but that’s just the beginning to the collision detection story. Consider the problem of determining if two objects have collided. Two of the most common approaches to this problem are bounding boxes and bounding spheres. For a bounding sphere, find the point furthest away from the center of the object. The distance of the point from the center defines the radius of the bounding sphere. You can see a bounding sphere around an object in Figure 6. Now imagine that the object and circle around it are actually 3D. As you can see, although the entire object is inside the sphere, it isn’t a snug fit. You can see how easy it would be to have an object that would hit the bounding sphere yet miss the object completely.

Axis-aligned bounding boxes, or bounding boxes for short, are another simple method for collision detection. The box is called axis-aligned because the sides of the box are parallel to the principle world x, y, and z axes. This reduces the check for collision to a simple minimum-maximum test. You create the box by determining the minimum and maximum extents in each dimension. The collision test then consists of:
IF ( (point.x >= box.minX and point.x <= box.maxX) and (point.y >= box.minY and point.y <= box.maxY) and (point.z >= box.minZ and point.z <= box.maxZ) )then a collision occurred.
Bounding boxes are a simple, fast way to check for rough collisions. However, like the bounding spheres, the fit is not necessarily very accurate. They’re generally used as a first test to check if further investigation is needed. You can improve the fit by maintaining a hierarchy of smaller bounding boxes or spheres that are tested after the initial collision is determined. For many games, such as 3D fighting games which require fairly detailed collision, this is enough for realism. If you need more detailed collision information, you need to look elsewhere. Other methods such as oriented bounding boxes (OBB), where the bounding box is allowed to rotate to an arbitrary orientation, will allow for a tighter fit than either above method. However, even OBBs do not provide information on the exact point of collision on an arbitrary mesh unless the object happens to be a box.
Getting to the Point #
If you really need to know which point of an object has collided with another — say for your realistic physics simulation — you have some work in front of you. All the other techniques are good first steps, and serve to filter out unneeded calculations. I’m going to start out in 2D again to make things easy. Let me begin by considering only convex objects. Remember that convex objects are polygon meshes that contain no interior angles greater than 180 degrees. Figure 7 outlines the problem.

I am interested in deciding whether polygon 1 is colliding with polygon 2. I could use my point-in-polygon test from earlier, and test every point in each polygon and see if it’s in the other. That wouldn’t be very efficient though, and in 3D it would be even less reasonable. What I really want to find is a single feature that makes it impossible for polygon 2 to be inside polygon 1. It turns out that if I can find a line that separates the two polygons, then they cannot be colliding. To make it easier, I will use the edges of each polygon as a test line. If all the vertices of polygon 2 are on the other side of an edge in polygon 1, they aren’t colliding.
You will recall from the convex point-in-polygon test that I used the dot product to determine if a point was inside an edge. I can use the same test to see if a point is outside. In other words, if vectorba dot vectorbc < 0, then point c is outside of polygon 1.
That dot product operation sure comes in handy. In 3D, you would be dotting the face normal with the test point, but it works out similarly. The first time you test to find this separating edge, you need to test all edges in each polygon against all the vertices in the opposite one. However, once you have found a separating edge, you can store this information so that edge is the first one you will test the next time you try. This caching of collision points can speed up testing quite a bit, and I highly recommend it.
That is all the time I have for this month. Next month, I will examine what exactly is required for detecting the collision point in 3D. I will also discuss what you can do with this information once you have it, and work out some cool samples to demonstrate it.
참고자료목록 #
- O'Rourke, Joseph. Computation Geometry in C. Cambridge University Press, 1993. A very good discussion of point-in-polygon strategies as well as path finding and convex hull operations (hint: may be handy).
- Heckbert, Paul S., Editor. Graphic Gems IV. Academic Press, 1994. Inside polygon strategies and routines.
- Baraff, David, and Andrew Witkin. "Physically Based Modeling," SIGGRAPH Course Notes, July, 1998, pp. D32 – D40. Collision detection and response methods.
첨부자료 1 - The Quadrant Approach to the Bounding Box Test #
// FIGURE OUT WHICH QUADRANT THE VERTEX IS RELATIVE TO THE HIT POINT
#define WHICH_QUAD(vertex,hitPos) \
( (vertex.x > hitPos->x) ? ((vertex.y > hitPos->y)
? 1 : 4) :( (vertex.y > hitPos->y) ? 2 : 3) )
// GET THE X INTERCEPT OF THE LINE FROM THE CURRENT VERTEX TO THE NEXT
#define X_INTERCEPT(point1, point2, hitY) \
(point2.x - ( ((point2.y - hitY) * (point1.x - point2.x))
/ (point1.y - point2.y) ) )
/////////////////////////////////////////////////////////////
// Procedure: PointInPoly (SUM OF ANGLES CROSSING VERSION)
// Purpose: Check if a point is inside a polygon
// Returns: TRUE if Point is inside polygon, else FALSE
/////////////////////////////////////////////////////////////
BOOL CFateView::PointInPoly(tSector *sector, tPoint2D *hitPos)
{
/// Local Variables /////////////////////////////////////////
short edge, first, next;
short quad, next_quad, delta, total;
/////////////////////////////////////////////////////////////
edge = first = sector->edge;
quad = WHICH_QUAD(m_edgelist[edge].pos, hitPos);
total = 0; // COUNT OF ABSOLUTE SECTORS CROSSED
/* LOOP THROUGH THE VERTICES IN A SECTOR */
do {
next = m_edgelist[edge].nextedge;
next_quad = WHICH_QUAD(m_edgelist[next].pos, hitPos);
delta = next_quad - quad; // HOW MANY QUADS HAVE I MOVED
// SPECIAL CASES TO HANDLE CROSSINGS OF MORE THEN ONE
//QUAD
switch (delta) {
case 2: // IF WE CROSSED THE MIDDLE, FIGURE OUT IF IT
//WAS CLOCKWISE OR COUNTER
case -2: // US THE X POSITION AT THE HIT POINT TO
// DETERMINE WHICH WAY AROUND
if (X_INTERCEPT(m_edgelist[edge].pos,
m_edgelist[next].pos, hitPos->y) > hitPos->x)
delta = - (delta);
break;
case 3: // MOVING 3 QUADS IS LIKE MOVING BACK 1
delta = -1;
break;
case -3: // MOVING BACK 3 IS LIKE MOVING FORWARD 1
delta = 1;
break;
}
/* ADD IN THE DELTA */
total += delta;
quad = next_quad; // RESET FOR NEXT STEP
edge = next;
} while (edge != first);
/* AFTER ALL IS DONE IF THE TOTAL IS 4 THEN WE ARE INSIDE */
if ((total == +4) || (total == -4)) return TRUE; else return FALSE;
}
첨부자료 2 - The X Intercept Calculation #
// Procedure: PointInPoly (EDGE CROSSING VERSION)
// Purpose: Check if a point is inside a polygon
// Returns: TRUE if Point is inside polygon, else FALSE
BOOL CFateView::PointInPoly(tSector *sector, tPoint2D *hitPos)
{
// Local Variables
short edge, first, next;
tPoint2D *pnt1,*pnt2;
BOOL inside = FALSE; // INITIAL TEST CONDITION
BOOL flag1,flag2;
edge = first = sector->edge; // SET UP INITIAL CONDITIONS
pnt1 = &m_edgelist[edge].pos;
flag1 = ( hitPos->y >= pnt1->y ) ; // IS THE FIRST VERTEX OVER OR UNDER THE LINE
/* LOOP THROUGH THE VERTICES IN A SECTOR */
do {
next = m_edgelist[edge].nextedge; // CHECK THE NEXT VERTEX
pnt2 = &m_edgelist[next].pos;
flag2 = ( hitPos->y >= pnt2->y ); // IS IT OVER OR UNDER
if (flag1 != flag2) // MAKE SURE THE EDGE ACTUALLY // CROSSES THE TEST X AXIS
{
// CALCULATE WHETHER THE SEGMENT ACTUALLY CROSSES THE X TEST AXIS
// A TRICK FROM GRAPHIC GEMS IV TO GET RID OF THE X INTERCEPT DIVIDE
if (((pnt2->y - hitPos->y) * (pnt1->x - pnt2->x) >=
(pnt2->x - hitPos->x) * (pnt1->y - pnt2->y)) == flag2 )
inside = !inside; // IF IT CROSSES TOGGLE THE INSIDE // FLAG (ODD IS IN, EVEN OUT)
}
pnt1 = pnt2; // RESET FOR NEXT STEP
edge = next;
flag1 = flag2;
} while (edge != first);
return inside;
}








![[http]](/wiki/imgs/http.png)
