Chapter 14. 충돌 처리 (1)
14.1 충격과 충격 토크
14.1.1 충격 토크
이 장에서는 회전 강체에 대해서 알아볼 것이다. 따라서 그 내용이 간단하지만은 않을 것이다.
만약 어떤 물체가 회전하는 동안 이를 튕겨낸다고 가정한다면, 물체가 일관된 방향으로 튀어 나가지도 않을뿐더러
그 각속도(angular velocity)도 변하게 마련이라는 것에 주의해야 한다.
우리는 충돌이 선속도와 각속도 모두에 어떤 영향을 미치는지를 이해할 필요가 있따.
그림 14.1은 회전하면서 땅으로 파고드는 막대를 보여주고 있다. (움직이는 두 물체의 충돌에 관한 문제는 곧 살펴볼 것이다)
충돌하는 바로 그 순간에 실제로 어떤 일들이 발생하는지 자세하게 살펴보자. 그림의 아랫부분은 충돌이 발생하는 시점에서
물체에 가해지는 변형을 설명하고 있다. 이는 그림에서 보여지는 방향으로 힘을 가하는 원인이 된다.
10장에서 살펴본 달랑베르의 원리에서 물체에 작용하는 모든 힘이 선속도와 각속도의 가속을 발생시키는 것을 알 수 있었다.
선 성분은 다음과 같다.
그리고 토크의 영향을 받는 각속도는 다음과 같다.
토크가 발생시키는 각가속도는 다음과 같다.
이들은 식 10.5와 동일하다.
따라서 충돌이 발생할 때는 선과 관련된 속도의 변경(충격)과 각과 관련된 속도의 변경이 동시에 발생된다.
속도의 즉각적인 각 변화를 충격 토크라고 부른다. 드물게는 충격 순간(momnet of impulse, impulsive moment)이라고 부르기도 하는데,
이는 마치 라스베이거스의 즉흥 결혼식 같은 뉘앙스로 들린다.
앞서와 동일한 방식으로
로 표시될 수 있으며, 토크는 다음과 같이 표시될 수 있다.
여기서 u는 충격 토크를 말하며, I는 관성 텐서, ˙θ는 각속도를 의미한다. 이는 선형 충격을 다루는 공식 7.6과 완전히 동일하다.
아울러 각속도의 변경값은 아래와 같이 표시될 수 있다.
앞서 10.2.3절에서도 논했듯이, 여기서 다루는 모든 방정식에서 월드 좌표를 사용하고 있다는 것에 주의해야 한다.
충격은 힘과 비슷하게 작용한다. 여기서 아주 짧은 순간 아주 짧은 거리에서 작용하는 힘을 표현하는 단어로 충격을 사용한다는 것을
잊지 말아야 한다. 앞서 살펴본 것처럼 충격에는 직선 성분과 각 성분이 존재한다. 토크의 총합은 다음과 같다.
충격에 의해 발생되는 충격 토크는 다음과 같다.
우리가 살펴보고 있는 충돌의 경우 적용점(pf)은 접촉 지점과 물체의 방향에 의해 다음과 같이 결정된다.
여기서 q는 월드 좌표에서 접촉이 발생한 지점을 의미하며, p는 월드 좌표에서 물체의 원점을 뜻한다.
14.1.2 회전하는 충격
충돌 당시 충격으로 인해 발생하는 효과는 두 물체를 서로 멀어지게 하는 것이다. 좀 더 정확하게 말하자면, 각각의 물체가
정확하게 충돌한 지점으로부터 서로 멀어지는 것이다. 그리고 이 지점은 우리가 이미 7장에서 살펴본 방정식에 따라 결정될 것이다.
충돌이 발생할 즈음 상대방에게서 멀어지고 있는 2개의 충돌 지점을 조사해 본다면 이들이 멀어지는 속도는 아마 다음과 같을 것이다.
여기서 Vs는 충돌이 발생하기 직전 물체의 상대 속도를 의미하며 Vs'는 충돌 이후의 상대 속도를 의미한다.
c는 복원 계수를 의미한다. 즉, 분리 속도는 항상 접근 속도의 반대 방향으로 발생하며 그 속도에 정수 비례한다.
정수값 c는 각 물체가 어떤 물질로 구성되었는지에 따라 달라진다.
물체의 성분과 접촉 법선의 방향에 따라 분리 속도의 직선운동과 회전운동이 결정된다.
그림 14.2는 동일하게 충돌하는 각기 다른 물체들을 보여주고 있다. 명확한 설명을 위해 모든 물체가 고정된 지표면에 충돌하는 것으로
설정한다. 각각의 그림에서 보여지는 접촉 지점에서의 접근 속도와 복원 계수가 동일하므로 분리 속도 역시 동일할 것이다.
첫 번째 물체는 가볍고 머리 부분부터 충돌한다. 충돌이 발생하면서 생성되는 모든 힘은 토크가 상대적으로 적을 것이다.
f가 pf와 거의 평행하기 때문이다. 이 경우 회전하는 부분은 거의 없으므로 선형으로 튕김이 수행된다.
두 번째 물체는 조금 더 무겁고 Z축으로 아주 작은 관성 모멘트를 가지고 있다. 이 경우 중심을 벗어난 지점에서 충돌이 발생한다.
여기서는 관성 모멘트가 아주 적으므로 발생하는 토크는 아주 클 것이며, 따라서 큰 회전 반응이 발생할 것이다.
이 경우 회전 반응이 너무나 커서 직선 성분은 물체를 위로 튕겨 올리지 못할 것이다. 비록 접촉 지점은 모든 다른 경우의 접촉 지점이
그런 것처럼 동일한 속도로 다시 튕겨서 올라가겠지만 분리되는 과정에서 가장 크게 작용하는 것은 물체의 회전이며,
따라서 직선운동은 조금씩 느려지면서 아래 방향을 향할 것이다. 설정된 경우와 같이 자(ruler)를 땅바닥에 떨어뜨려보면 이런 현상을
관찰할 수 있을 것이다. 자가 땅바닥에 닿는 순간 접촉 지점에서부터 회전하면서 멀어지기 시작할 것이다.
자 전체가 땅바닥에서 튕겨 올라가지는 않는다. 회전이야말로 접촉 지점에서 멀어지는 힘의 상당 부분을 차지하고 있는 것이다.
세 번째 충돌은 두 번째와 같은 방식으로 이루어진다. 대부분이 거의 유사하지만 관성 모멘트는 훨씬 더 크다.
세 번째 충돌에서 보이는 물체는 두 번째 물체에 비해 양쪽 끝이 훨씬 커 보인다. 따라서 선 충격량이 더 크고, 충격 토크는 작다.
물체는 직선상으로 튀어 올라가며 압축력은 회전의 방향을 반대로 가한다. 그 결과 각속도는 작아진다.
14.1.3 회전 충돌 처리하기
입자 충돌의 경우 충돌 반응을 얻기 위해 2개의 과정이 필요하다.
첫째, 충격량과 충격 토크를 적용함으로써 물체 2개의 상대적인 움직임을 분석해야만 한다. 속도라는 측면에서 충돌을 분석하기 위해서는
4가지 값을 알아야만 한다. 충돌하는 2개의 물체에 대한 충격량과 충격 토크가 바로 그것이다. 선 충격량과 각 충격량의 비율을 계산하는 것이
가장 주요한 수학적 과제라고 할 수 있다.
각 프레임의 마지막 부분에서 충돌을 계산하기 때문에 각 물체들은 서로를 통과해 지나갔을 수도 있다.
이로 인해 발생하는 모든 상호 관통을 하나하나 처리할 필요가 있는 것이다. 물체 간의 상호 관통 역시 입자 간의 상호 관통을 다루는 것과
유사한 방식으로 처리될 수 있다. 첫 단계에서 수행해야 하는 충격량 계산으로 인해 좀 더 물리적으로 현실에 가까운 상호 관통 처리를
할 수 있게 된다. 14.3절에서 이 프로세스에 대해 다뤄볼 것이다.
14.2 충돌 충격량
두 가지 물체의 상대적인 움직임을 처리하기 위해서는 각 물체에 대한 선 충격량과 각 충격량을 포함한 충격량을 계산할 필요가 있다.
만약 물체가 지표면과 같이 고정되어 물체와 부딪히는 경우처럼 하나의 물체만 신경 쓰면 된다면 단지 2개의 값,
즉 움직이는 물체의 충격량과 충격 토크를 구하는 것만으로도 충분하다.
각 물체의 충격량과 충격 토크를 계산하기 위해서는 다음과 같은 단계를 밟아야만 한다.
1. 접촉과 관련된 일련의 좌표를 구한다. 이를 통해 복잡한 수학 계산 과정을 단순하게 처리할 수 있다.
따라서 첫 과정으로 변환을 위한 회전 행렬을 만들고, 여기에서 새로운 일련의 좌표를 도출해낸다.
2. 각각의 충격 단계마다 2개의 물체상에 존재하는 접촉 지점에서의 속도 변화를 처리한다.
충격량은 선운동과 각운동을 발생시키는 원이이 되기 때문에 충격량 역시 이 두 운동을 감안할 필요가 있다.
3. 앞 단게에서 도출된 결과를 변환해 속도 변화에 필요한 충격량을 찾는다.
4. 접촉 지점에서의 분리 속도와 현재의 접근 속도를 찾아낸다. 이 두 값의 차이가 기대되는 속도의 변화량이다.
5. 요구되는 속도 변화량을 기반으로 발생해야 하는 충격량을 계산해낸다.
6. 충격량을 기반으로 선 성분과 각 성분을 분리해 내고 이를 각각의 물체에 반영한다.
이제 각각의 단계를 순서대로 살펴보자.
14.2.1 접촉 좌표 변경하기
이 시점에서 우리에게 필요한 것은 충돌의 결과로 어느 정도의 충격량이 발생하는지 구해내는 것이다.
충격량은 속도의 변화를 유발한다. 속도의 변화를 계산하기 위해서는 우선 이를 유발하는 충격량을 알아낼 필요가 있다.
이 단계에서 각 물체의 선속도와 각속도는 큰 의미가 없다. 오직 충돌 지점에서 발생하는 분리 속도를 구하는 것만이 의미가 있다.
앞장에서도 살펴보았듯이, 최종적인 분리 속도를 계산하는 방정식이 이미 존재한다. 따라서 간단하게 이를 적용시키기만 하면 된다.
식 9.6에서 다룬 것과 같이, 각 물체의 접촉 지점에서의 속도는 선속도 및 각속도와 관련되어 있다.
이 단계에서는 충돌 지점에서 일어나는 운동에만 집중하면 되므로 충돌 지점과 연관된 계산만 수행함으로써 일련의 수학적인 과정을
단순화할 수 있다. 13장에서 살펴본 바와 같이 각각의 접촉은 이와 관련된 접촉 지점과 접촉 법선을 가진다는 것을 상기해 보자.
만약 이 접촉 지점을 원점으로 사용하고 접촉 법선을 하나의 축으로 사용한다면 그 주변으로 직교 기저(orthogonal basis)를 형성할 수 있다.
월드 좌표와 각 물체의 로컬 좌표를 알고 있으므로 각 접촉에 대한 접촉 법선을 구할 수 있을 것이다.
그림 14.3은 하나의 접촉에서 볼 수 있는 접촉 좌표들을 보여주고 있다. 이 단계에서 상호 관통은 전혀 다루지 않고 있다는 것에 주의해야한다.
여기서는 각각의 접촉에서 단 하나의 지점만을 계산했다.
접촉 좌표 축
접촉 좌표 변환의 첫 단계는 각 축의 방향을 결정하는 것이다. 2.1.9절에서 살펴본 것과 같이 이 부분은 직교 기저를 계산하는 알고리즘을 사용해
처리할 수 있다. X축은 우리가 이미 알고 있는 바와 같이 충돌 감지기에 의해 생성되는 접촉 법선이다. Y축과 Z축은 추가로 계산될 필요가 있다.
하나의 X축에서 여러 개의 Y축과 Z축이 생성될 수 있다. 우리는 단 하나의 Y축과 Z축이 필요할 뿐이다.
만약 여러 방향에서 서로 다르게 적용되는 이방성 마찰을 감안해야 한다면 단 한 세트의 기저 벡터만 존재할 것이며,
그것이 가장 사용하기에 적합한 기저 벡터가 될 것이다. 이 책에서 등방성 마찰을 다루고 마찰이 시뮬레이션보다 적은 경우라면
어떤 기저 벡터라도 사용이 가능할 것이다. 일단 지금 단계에서는 마찰을 고려하지 않으므로 월드 좌표의 Y축으로부터 도출된 Y축을 시작으로
여러 개의 임의의 축을 만들어 낼 수 있다. 알고리즘이 기본 축을 필요로 한다는 것을 상기할 필요가 있다.
여기서는 X축이 되겠다. 이를 기반으로 두 번째 축을 유추해 낼 수 있다. 하지만 유추한 것에 비해 어느 정도의 수정은 반드시 가미되기 마련이다.
2.1.9절에서 2개의 기본 축을 사용해 직교 기저를 만드는 알고리즘을 살펴보았다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /** * Creates an orthonormal basis where the x-vector is given * and the y-vector is suggested, but can be changed. Both * y and z vectors are written to in this function. We assume * that the vector x is normalized when this function is called. */ void makeOrthonormalBasis(const Vector &x, Vector *y, Vector *z) { // Calculate z from the vector product of x and y. z = (*x) % (*y); // Check for y and z in parallel if (z->squaredMagnitude() == 0.0) return; // Calculate y from the vector product of z and x. (*y) = (*z) % x; // Normalize the output vectors y->normalize(); z->normalize(); } | cs |
이 경우 이와 똑같은 알고리즘을 사용할 수 있으나, Y축 값에 추가적인 요소를 더할 필요가 있다. Y축과 Z축의 방향을 고려하지 않고 있기 때문에
어떤 Y축이라도 선택이 가능하기 때문이다. 예를 들어, 월드 좌표의 Y축은 다음과 같다.
1 2 | Vector y(0, 1.0, 0), z; makeOrthonormalBasis(contactNormal, &y, &z); | cs |
벡터곱을 수동으로 수행함으로써 기나긴 작업의 효율을 향상시킬 수 있다. 오히려 Vector3 클래스의 벡터곱 연산자를 호출하는 것보다 이 편이
더 효율적이다. 최초의 Y축이 Y축을 따라 설정된다면 그 결과로 도출되는 모든 Z축은 반드시 Y축과 직각을 이루어야 할 것이다.
이는 Z축이 어떤 Y 성분도 가지지 않을 때에만 적용된다. 이 경우 Z축의 Y 좌표 값들은 별도 계산없이 바로 0으로 설정이 가능하다.
코드는 다음과 같다.
1 2 3 4 5 6 7 8 9 10 11 12 | // The output axes Vector y, z; // Scaling factor to ensure the results are normalized. const real s = 1.0/real_sqrt(x.z*x.z + x.x*x.x); // The new Z axis is at right angles to the world Y axis. z.x = x.z*s; z.y = 0; z.z = -x.x*s; // The new Y axis is at right angles to the new X and Z axes. y.x = x.y*z.x; y.y = x.z*z.x - x.x*z.z; y.z = -x.y*z.x; | cs |
여기서 추가적인 설명이 필요한 문제가 하나 더 있다. 만약 지나가는 접촉 법선이 X축으로서 이미 월드 좌표의 Y축의 방향을 지시하고 있다면,
Z축의 3가지 성분값은 모두 0으로 설정이 가능할 것이다. 이 경우 월드 좌표의 Y축을 사용한다는 것은 그리 좋은 생각은 아니다.
다른 것으로 이를 대체할 필요가 있다. Y축을 대신해 월드 좌표의 X축과 Z축을 시험해 보는 것도 좋은 생각이다.
이들이 서로 직각으로 교체하는 한 방향은 상관이 없다는 것을 다시 한번 상기하도록 하자.
구현되어 있는 코드는 월드 좌표의 X축을 사용한다. 알고리즘을 가능한 한 안정적인 것으로 만들기 위해 어떤 것이 접촉 법선에 가까운지에 따라
X축과 Y축을 번갈아 사용할 수 잇는 변환 구조를 만들 것이다. 만약 접촉 법선이 정확하게 Y축의 방향과 일치한다면 이 구조를 그대로
사용할 수 있겠지만, 사실상은 아주 가깝게 위치하고 있는 것뿐이며 이로 인해 계산의 오차가 발생하고 부정확한 결과를 초래할 수 있게 된다.
이 경우를 대비해 벡터적(cross-product)의 사이즈를 최대화하는 것이 좋다.
1 2 3 4 5 6 7 8 9 10 | if (real_abs(x.x) > real_abs(x.y)) { // We’re nearer the X axis, so use the Y axis as before. // ... } else { // We’re nearer the Y axis, so use the X axis as a guess. // ... } | cs |
기저 행렬
기저를 계산하는 코드를 완성하기 전에 어떤 산출물이 필요한지 우선 살펴볼 필요가 있다.
지금까지 우리는 하나의 직교 기저(orthonormal basis)를 완성하는 데 필요한 3가지 벡터가 있다고 가정해 왔다.
3가지 벡터를 따로 계산하는 것보다 행렬을 사용하는 것이 훨씬 간단하다. 9.4.2절에서 살펴본 행렬이 일련의 축들을 다른 것으로 변환하는 데
사용되는 바로 그것이다.
9.4.2절에서 살펴본 것과 같이 로컬 공간을 월드 공간으로 변환하는 변환 행렬은 3개의 로컬 공간 축을 행렬의 열에 들어가는 값으로 입력해
만들어진다. 따라서 3개의 벡터 값으로 구성되는 직교 기저를 만든다면 아마 다음과 같을 것이다.
이들을 변환 행렬로 조합하면 다음과 같다.
만약 로컬 공간에서의 위치를 표시하는 좌표 값을 가지고 있고, 동일한 지점을 가리키는 월드 공간의 좌표 값을 계산하기를 원한다면
변환 행렬에 좌표 벡터를 곱해주기만 하면 된다.
즉, 바꿔 말하면 기저 행렬은 로컬 좌표를 월드 좌표로 변환해 주는 기능을 수행한다고 볼 수 있는 것이다.
14.2.2 충격에 의한 속도 변화
충돌 지점에서 발생하는 압축과 변형에 대한 저항으로 인해 발생하는 힘 때문에 충돌이 발생할 때 두 물체의 움직임이 변화한다는 사실을
기억해야 한다. 충돌이 발생할 때 생기는 모든 일들을 아주 짧은 한순간에 모두 표현해야 하므로 힘 대신에 충격을 사용한다.
충격은 속도의 변화를 발생시킨다. 달랑베르의 원리에 따르면 충격은 힘과 마찬가지로 선속도와 각속도 모두를 변경시킨다.
따라서 충돌이 발생했을 때 그 충격량을 계산하는 것이 우리의 목표라면 이 충격이 각 물ㅊ레에 어떤 영향을 미치는지를 우선 이해할
필요가 있다. 또한 이를 수학적으로 정리할 수 있다면 충격량에 따라 각각의 물체에는 어떤 속도의 변화가 발생하는지를 이해할 수 있게 될 것이다.
이 장에서 우리가 다루고 있는 마찰력 없는 접촉이라는 측면에서는 접촉 시 발생하는 유일한 충격은 접촉 법선을 따라 발생한다.
이를 간단한 숫자로 변환이 가능하다면 이를 통해 접촉 법선의 방향으로 발생하는 속도의 변화를 예측할 수 있고,
충격이 이와 동일한 방향으로 어떻게 적용되는지도 알 수 있을 것이다.
앞서 살펴본 것처럼, 각 단위 임펄스에서 발생하는 속도 변화는 두 가지의 성분을 가진다. 선 성분과 각 성분이 그것이다.
이들을 각각 분석해 보고 그 다음에 그 결과를 조합해 보기로 한다.
각 물체의 성분에 의해 변경되는 값들은 아무 의미가 없다. 우리에게 필요한 것은 충돌이 발생할 때 각각의 물체에 가해지는 선속도와
각속도의 변화량이다.
선 성분
선 성분은 아주 간단하다. 단위 임펄스에 대한 속도의 선 변화는 충격과 같은 방향으로 주어진 역 질량(inverse mass) 정도만큼 발생한다.
두 개의 물체가 일으키는 충돌에서 선 성분은 간단하게 두 역질량의 합으로 표시될 수 있다.
이 방정식이 오직 선 성분의 속도에만 유효하다는 것을 기억하라. 필요한 모든 값이 다 구해진 것이 절대 아니라는 것을 명심해야 한다.
각 성분
각 성분을 구하는 것은 조금 더 복잡하다. 앞서 살펴보았던 3개의 방정식을 한꺼번에 계산해야 한다. 조금 더 계산을 간단하게 수행하기 위해
물체의 원점에 대비한 접촉 지점을 q rel이라고 표시한다.
첫째로 식 14.2를 사용해 단위별 임펄스에서 발생하는 임펄스 토크의 총합을 구할 수 있다.
여기서 d는 충격의 방향이며, 이 경우는 접촉 법선을 의미한다.
둘째, 식 14.1을 사용해 각 단위별 임펄스 토크의 각속도 변화를 측정할 수 있다.
그리고 마지막으로 식 9.6을 사용해 접촉 지점에서의 속도 총합을 구할 수 있다. 선 성분을 제거할 수 있다면 오직 회전만 존재하므로
접촉 지점에서의 선속도를 계산할 수 있는 방정식을 얻을 수 있다.
접촉 지점에서의 회전 유도 속도(˙q)는 물체의 원점에 대비한 접촉 지점의 위치(q-p)와 물체의 각속도(˙θ)에 따라 달라진다.
이로써 단위별 임펄스와 이를 통해 생성되는 임펄스 토크, 그리고 토크를 통해 생성되는 각속도와 그 결과로 얻을 수 있는 선속도를 계산할 수 있는
일련의 공식이 마련되었다. 이 3가지 공식을 코드로 구현하면 다음과 같다.
1 2 3 4 5 6 | Vector3 torquePerUnitImpulse = relativeContactPosition % contactNormal; Vector3 rotationPerUnitImpulse = inverseInertiaTensor.transform(torquePerUnitImpulse); Vector3 velocityPerUnitImpulse = rotationPerUnitImpulse % relativeContactPosition; | cs |
단위 임펄스의 회전에 의해 결정되는 속도가 결과로 도출될 것이다. 현 상태 그대로 결과는 월드 공간에서의 속도가
그러하듯 벡터로 표시될 것이다. 하지만 정작 우리가 관심을 가지고 있는 것은 접촉 법선상에서의 속도이다.
앞서 살펴본 변환 기저 행렬을 활용해 이런 벡터를 접촉 좌표로 변환할 필요가 있다. 이를 통해 단위 임펄스가 원인이 되는 속도의 벡터를 구할 수
있게 된다. 하지만 궁극적으로 우리가 얻고자 하는 것은 접촉 법선의 방향으로 적용되는 속도이다. 접촉 좌표에서 이는 X축으로 설정된다.
따라서 우리가 얻고자 하는 값은 결과로 도출되는 벡터의 X 성분이다.
1 2 3 | Vector3 velocityPerUnitImpulseContact = contactToWorld.transformTranspose(velocityPerUnitImpulse); real angularComponent = velocityPerUnitImpulseContact.x; | cs |
여기 transformTranspose 메소드는 행렬을 변환함으로써 벡터를 편리하게 변환해준다.
하지만 이를 더 빠르게 수행할 수 있도록 해주는 방법이 있다. 만약 아래와 같은 행렬의 곱셈을 수행한다고 해보자.
이 경우 결과의 X 성분은 xa + yb + zc 가 될 것이다. 이는 스칼라곱과 같으므로 다음과 같이 표시할 수 있다.
접촉 좌표이 경우 벡터는 다음과 같다.
이는 우리가 기저 행렬을 살펴볼 때 보았던 접촉 법선과 동일하다. 따라서 이 전체 과정을 다음과 같은 코드로 표현할 수 있다.
1 2 | real angularComponent = velocityPerUnitImpulse * contactNormal; | cs |
모두 합치기
이제 충돌이 발상할 때 각 물체에 대해 각 단위 임펄스마다 발생하는 접촉 지점에서의 속도 변화를 측정할 수 있게 되었다.
두 물체가 접촉할 때 우리는 4개의 값을 측정할 수 있다. 각각의 물체에 대해 선운동과 각운동에 의해 발생하는 속도가 바로 그것이다.
움직이지 못하는 바닥에 접촉하는 경우와 같이 하나의 강체만이 유효한 접촉의 경우에는 2개의 값만 측정 가능할 것이다.
14.2.3 속도에 의한 충격량 변화
마찰이 없는 충돌을 생각한다면 이 단계는 간단하기 그지없다. 단위 임펄스 대비 속도 변화에 대한 단일 값을 d라 부른다면 주어진
속도 변화에 따라 필요한 충격량은 아래와 같이 계산할 수 있다.
g = v/d [식 14.3]
여기서 v는 필요한 속도 변화이며, g는 필요한 충격량이다.
14.2.4 필요한 속도 변화 계산하기
이 단게의 알고리즘은 크게 두 단계로 나뉜다. 첫째로, 접촉 지점에서의 현재 접근 속도를 계산할 필요가 있다.
두 번째로, 우리에게 필요한 속도의 변화가 어느 정도인지를 계산해야 한다.
접근 속도 계산하기
필요한 속도의 변화량을 계산하기 전에, 접촉 지점에서의 접근 속도가 어느 정도인지를 우선 파악해야 한다.
앞서 살펴본 바와 같이 속도는 선 성분과 각 성분 모두를 가진다. 접촉 지점에서의 한 물체의 속도 총합을 계산하기 위해서는
이 두 가지가 모두 필요하다. 오직 회전을 활용해 선속도와 접촉 지점에서의 선속도를 계산할 수 있다.
물체로부터 직접 선속도를 구할 수 있으며 이는 강체에 저장된다. 회전으로 인해 발생하는 속도를 구하기 위해서는
식 9.6을 사용할 필요가 있다. 접촉 지점에서 한 물체에 발생하는 속도의 총합은 다음과 같다.
1 2 | Vector3 velocity = body->getRotation() % relativeContactPosition; velocity += body->getVelocity(); | cs |
만약 두 개의 물체가 충돌한다면 속도 벡터에 두 번째 물체에 관한 값들이 추가되어야 할 것이다.
위의 코드를 통해 월드 좌표상에서 발생하는 접근 속도의 총합을 알 수 있다. 접촉 법선의 방향으로 발생하는 속도가 어느 정도인지,
그리고 이에 접하는 접선상에서의 속도 변화는 어느 정도인지를 알아내야 하므로 결국 우리가 필요한 것은 접촉 좌표상에서의 값이 될 것이다.
접촉 법선의 방향상에 있지 않는 속도의 성분들은 물체가 얼마나 빠른지를 표시하며, 각각의 값이 정신 없이 변할 것이다.
이들은 추후 우리가 마찰을 고려할 때 아주 중요한 요소로 자리매김할 것이다.
이제는 익숙해진 아래와 같은 방식으로 기저 행렬을 사용한 변환이 수행될 것이다.
1 | contactVelocity = contactToWorld.transformTranspose(velocity); | cs |
마찰이 없는 충돌에서는 이렇듯 접촉 법선의 방향으로 설정된 벡터의 성분만을 활용한다.
벡터는 접촉 좌표상에 존재하고, 이 값은 간단하게 벡터의 X 성분으로 표시가 가능하기 때문이다.
필요한 속도 변화 계산하기
이 장의 첫 부분에서도 언급했듯이, 우리가 구하고자 하는 속도의 변화는 앞서 입자 충돌에서 사용했던 것과 동일한 공식을 사용해 구할 수 있다.
이는 즉, 접촉 발생 지점에서 존재하는 모든 접근 속도를 제거해야 할 필요가 있다는 것을 의미하며, 최종 속도는 원래 값의 c배 만큼 커지고
반대 방향으로 작용한다는 것을 의미한다. 코드에서는 아래와 같이 간단하게 표현이 가능하다.
1 | real deltaVelocity = -contactVelocity.x * (1 + restitution); | cs |
만약 반발 계수가 0이라면 속도의 변화는 존재하는 모든 접근 속도를 상쇄할 정도로 충분할 것이다.
하지만 더 이상의 추가적인 작용은 발생하지 않는다. 이는 즉, 물체가 서로 멀어지지 않는 상태로 충돌이 마무리된다는 것을 의미한다.
만약 반발 계수가 1에 가깝다면 물체는 접근 속도와 거의 유사한 속도로 충돌 지점에서 분리되어 멀어질 것이다.
계수의 값은 충돌하는 물체의 성분이 어떤 것인가에 따라 달라진다. 0.4정도의 값은 그 반동이 아주 큰 편인데, 이는 딱딱한 바닥에
고무공이 부딪힐 때의 경우와 비슷하다. 이 ㄱ밧을 초과하는 경우는 현실에서 거의 발생하지 않는다.
14.2.5 충격량 계산하기
필요한 속도 변화의 값을 얻었다면 식 14.3을 통해 충격량을 구할 수 있다. 지금은 마찰을 고려하지 않으므로 접촉 법선의 방향으로만
적용되는 충격량만을 고려한다. 접촉 좌표에서 접촉 법선은 X축이며, 최종적인 충격량 벡터는 다음과 같이 계산될 수 있다.
여기서 g는 앞서와 같이 충격량을 의미한다. 이 단계에서는 접촉 좌표를 월드 좌표로 바꾸어 계산하는 것이 편리하다.
이를 통해 마지막 단계에 충격량을 반영하는 작업이 좀 더 용이해 질 수 있다. 좌표 변환을 위해 기저 행렬을 사용하면
아래와 같이 변환이 가능하다.
월드 좌표를 통해 계산된 충격량을 가지고 다음 단계를 진행할 것이며, 이를 충돌을 발생시킨 물체에 적용할 것이다.
14.2.6 충격량 적용하기
충격량을 적용하기 위해서는 식 7.6과 14.1이 필요하다. 첫 번째 공식은 물체의 선충격이 어떻게 선속도를 변환시키는지를 보여준다.
충돌한 첫 번째 물체의 속도 변화는 다음과 같다.
1 | Vector3 velocityChange = impulse * body[0]->getInverseMass(); | cs |
식 14.1에 의한 회전 변화는 다음과 같다.
우선 식 14.2를 다시 활용해 충격 토크 u를 계산할 필요가 있다.
코드에서는 다음과 같이 구현될 것이다.
1 2 3 | Vector3 impulsiveTorque = impulse % relativeContactPosition; Vector3 rotationChange = inverseInertiaTensor.transform(impulsiveTorque); | cs |
이 부분은 충돌한 첫 번째 물체에 적용할 부분이며, 두 번째 물체에도 이 부분이 바로 적용되는 것은 아니다.
지금까지 우리는 충돌 감지기에서 생성된 접촉 법선을 활용해 왔다. 룰에 따라 충돌 감지기는 첫 번째 물체의 관점에서 접촉 법선을 만들어 낸다.
따라서 이런 식으로 계산해 낸 충격량을 첫 번째 물체에 적용하는 것은 아무 문제가 없을 것이다.
두 번째 물체는 동일한 충격량을 반대 방향으로 받아야 한다.
첫 번째 물체의 계산에 사용된 동일한 코드를 두 번째 물체에도 적용이 가능하지만, 모든 충격량의 부호를 바꾸어줄 필요가 있다.
1 2 3 4 5 | // Calculate velocity and rotation change for object one. // ... impulse *= -1; // Calculate velocity and rotation change for object two. // ... | cs |
마지막으로 각각의 물체에 대해 계산된 속도와 회전 변화는 아래와 같이 강체의 속도와 각 속도에 바로 적용될 수 있다.
1 2 | body->velocity += velocityChange; body->rotation += rotationChange; | cs |
14.3 상호 관통 처리하기
지금까지 충돌이 발생했을 때의 속도 변화를 어떻게 처리할 것인지에 대해 알아보았다.
만약 시뮬레이션하는 물체가 충분히 단단하다면 이 모든 과정이 필수적으로 처리되어야 할 것이다.
하지만 충돌이 발생하는지 미처 감지하기도 전에 하나의 물체가 다른 물체를 관통하는 경우도 발생한다.
시뮬레이션이 정상적으로 동작한다고 해도 그 동안은 이를 체크하는 과정이 발생하지 않을 것이다.
충돌이 발생하는지를 체크하는 마지막 부분에서는 이미 두 개의 물체가 서로 접촉하고 하나가 다른 하나를 관통했을 수도 있다.
어떻게든 이런 방식의 상호 관통을 처리할 필요가 있다. 이를 제대로 처리하지 못한다면 게임 내에 등장하는 모든 물체들이
아주 단단한 물질로만 구성되어야 할 것이다.
이는 수많은 게임 엔진에서 공통적으로 요구되는 부분이다. 2개의 물체가 상호 관통하는 경우 이들은 아주 쉽게 서로 분리될 수 있다.
그들이 더 이상 교차하지 않는 처음 지점까지 접촉 법선을 따라 각각의 물체를 움직이면 된다.
14.3.1 처리 방법 선택하기
회전하는 강체를 다루어야 한다면 상황은 조금 더 복잡해진다. 상호 관통을 처리하기 위해 선택할 수 있는 전략이 다수 존재하기 때문이다.
간략하게 정리하면 다음과 같다.
선형 투영
이전에 살펴본 것과 동일한 알고리즘을 사용할 수 있는 방법도 있다. 바로 각 물체의 위치를 서로 바꾸는 것이다.
이를 통해 각 물체들은 접촉 법선의 방향을 따라 분리되어 이동할 수 있게 된다. 이때 운동량의 총합은 가능한 한 작아야 하며,
이를 통해 물체들은 더 이상 접촉하지 않아야 한다. 두 물체가 충돌하는 경우 각 물체의 운동 총량은 역질량에 비례한다.
따라서 가벼운 물체가 무거운 물체보다 더 많은 운동량을 가지게 되는 것이다.
이 방법은 원할하게 잘 동작하고 구현도 쉽다. 사실 대부분의 게임 엔진에서 이를 구현하는 코드가 동일하다.
하지만 이 방법이 현실을 제대로 반영하는 것은 아니다. 그림 14.4는 다른 충돌의 결과로 바닥에 떨어지는 블록을 보여주고 있다.
여기서 선형 투영(linear projection)을 사용하게 되면 충돌 이후의 상황이 그림에서 보여지는 것처럼 처리된다.
이는 실제 박스가 어떻게 움직이는지를 보여주는 세 번째 그림과는 다른 모습을 보여주고 있다.
선형 투영을 사용할 경우 물체들이 어색하게 떨리는 것처럼 보일 수도 있다. 만약 구형의 물체들만을 처리한다면 이 방법이 빠르고 효율적이다.
하지만 다른 물체를 처리하려면 좀 더 정밀한 방법이 필요하다.
속도 기반 처리
물리 엔진에서 사용되는 또 다른 기법 중의 하나는 충돌 시 물체에서 발생하는 선속도와 각속도를 고려하는 것이다.
이들이 움직이는 동안 어떤 지점에서 이 두 물체는 접촉하게 될 것이다. 그때부터 상호 관통이 시작되는 순간까지는 이 접촉 상태가 유지된다.
상호 관통을 처리하기 위해 이 두 물체를 첫 번째 충돌이 발생하는 지점 뒤로 거꾸로 움직일 수 있다.
사실 첫 번째 충돌의 이 지점을 계산하는 것은 쉬운 일이 아니지만 걱정할 일은 아니다.
충돌 감지기에 의해 생성되는 각 물체의 접촉 지점을 이 지점으로 유추할 수 있기 때문이다. 이 두 지점을 접촉 법선의 방향에 따라
그들이 움직인 경로에 맞추어 더 이상 겹치지 않을 때까지 이동시킬 수 있다.
물체를 거꾸로 이동시키기 위해서는 충돌 처리가 시작되기 전에 각 물체의 속도와 회전에 대해 추적해 볼 필요가 있다.
각 접촉 지점이 포함된 경로를 구할 수 있는 방정식을 푸는 데 이 값을 사용할 수 있고,
또한 상호 관통이 시작될 때처럼 이들이 언제 교차했는지도 알 수 있다.
이 방법은 합리적인 전략이면서 동시에 이를 통해 양질의 결과를 얻을 수 있지만, 시뮬레이션을 수행하는 도중에 추가적인
마찰을 발생시킬 수 있다는 부작용도 함께 가지고 있다. 그림 14.5가 한 예를 보여주고 있다.
이 그림에서 물체는 땅 윗부분을 빠르게 움직이면서 관통한다. 속도 기반 처리 방법을 사용하면 그림에서 보이는 것처럼 이 물체를
지나온 경로를 따라 뒤로 움직이게 된다. 이 경우 충돌에 대한 마찰이 전혀 고려되지 않았음에도 사용자에게 물체가 땅에 부딪치고 꽂힌 것처럼
보일 수 있다.
비선형 투영
3번째 옵션이자 이 장에서 택할 방법은 선형 투영에 기반을 두고 있다. 하지만 선을 따라 단순히 물체를 뒤로 움직이는 것이 아니라
선운동과 각운동의 조합을 활용해 관통을 처리하는 방법이다.
이론은 동일하다. 2개의 물체가 더 이상 관통하지 않을 때까지 접촉 법선의 방향을 따라 두 물체를 움직이는 것이다.
이 운동에는 선 성분뿐만 아니라 각 성분까지 포함된다.
충돌하는 각각의 물체에 대해서 선운동뿐만 아니라 각운동의 총량도 계산될 필요가 있으며,
최종적으로는 상호 관통 문제를 처리할 수 있는 것보다 충분히 큰 운동량이 도출되어야 할 것이다.
선형 투영에서는 각 물체의 총 운동량은 각 물체의 역질량에 따라 달라졌다. 이런 선형 투영과는 달리,
선속도와 각속도의 균형은 각 물체의 역관성 텐서(inverse iner-tia tensor)에 따라 달라진다.
접촉 지점에서 높은 관성 모멘트 텐서를 가지는 물체는 회전하는 힘이 상대적으로 덜하므로 대부분의 운동이 선운동의 형태를 띨 것이다.
만약 물체가 쉽게 회전하는 성질을 가지고 있다면 각운동이 차지하는 부분이 늘어날 것이다.
그림 14.6은 앞서 살펴본 동일한 상황에 이러한 논리가 적용된 것을 보여주고 있다.
그 결과는 여전히 현실에서 발생하는 것과 정확하게 일치하지는 않지만 좀 더 현실에 가깝고 어색해 보이지 않는다.
그림 14.7은 충돌이 발생한 바로 그 순간을 보여주고 있다. 비선형 투영은 추가적인 마찰을 생성해 내지 않는다.
오히려 물체가 미끄러지는 거리를 조금 더 늘려줌으로 인해서 약간의 마찰이 더 줄어들 수 있다.
현실에서는 줄어드는 양이 적어서 추가적으로 마찰이 발생하는 것보다 인지하기 힘들다.
이 장의 뒷부분에서 이 방법을 어떻게 구현할지에 대해 자세히 설명할 것이다.
완화법
완화법은 엄밀하게 말하면 아주 새로운 방법은 아니다. 완화법은 상호 관통의 일정 부분만을 처리해 줄 수 있으며,
다른 처리 방법과 함께 사용이 가능하다. 일반적으로 비선형 투영과 가장 많이 조합되어 사용된다.
완화법은 한 물체에서 여러 번의 접촉이 발생할 때 유용하게 사용이 가능하다. 각각의 접촉이 처리되는 과정에서 다른 물체들에서
발생하는 상호 관통이 함께 적용되는 것으로 처리할 수 있다. 벽돌이 쌓여 있는 벽의 경우 벽돌이 어느 방향으로 움직이더라도
다른 벽돌과 상호 관통이 발생할 수 있을 것이다. 이런 경우에 이런 기법이 유용하게 사용될 수 있다.
이런 경우 어떤 것이 먼저 처리되어야 하는지에 대한 순서가 문제가 될 수 있다. 따라서 특정한 접촉에 대해서는 여전히 더 많은 신경을
써야 할 필요가 있다.
상호 관통을 처리하는 단계가 늘어갈수록 개별 상호 관통에 대한 처리 내용도 증가하고, 일련의 접촉들에 대해 점점 더 명확한 그림을
그려나갈 수 있게 된다. 각각의 접촉에 대한 처리가 순서대로 조금씩, 수없이 반복되며 진행되는 것이다.
한 번 혹은 두 번의 명확한 접촉이 사전에 발생하는 경우라면 이 방법은 접촉이 발생하는 모든 지점에 해당 상호 관통의 정보를 반영하게 되며,
이 경우는 크게 신경을 쓰지 않아도 된다.
거의 충돌이 발생하지 않을 경우에 완화법을 사용하게 되면 마지막 부분에 상호 관통을 더욱 눈에 띄게 만드는 경향이 있다.
다른 기법에서 처리하기 힘든 접촉이 발생할 경우에는 모든 접촉이 일정 정도의 상호 관통에 관한 정보를 공유하도록 하는 것이 의미가
있을 것이다. 하지만 대부분의 경우에는 이 과정이 불필요하고 또한 충돌을 처리하는 모든 과정을 제대로 밟는 것이 시각적으로도
만족도가 높을 것이다.
완화법을 엔진에 반영하는 것은 상대적으로 어려운 일은 아니다. 일반적인 충돌 처리 알고리즘을 수행하기 전에 관통을 처리하는 부분을
고정된 값만큼 복사해 주면 그만이다. 일단은 완화법이 적용되지 않은 기본적인 시스템을 구축할 것을 권장하고 싶다.
만약 완화법이 필요한 상황이 오면 그때 가서 이 부분만 추가해도 문제될게 없을 것이다.
'Game > Physics & Math' 카테고리의 다른 글
Real Time Collision Detection - Chapter 5: Basic Primitive Tests (1) (0) | 2018.12.04 |
---|---|
Game Physics Engine Development - Chapter 14 : 충돌 처리 (2) (0) | 2018.12.03 |
Game Physics Engine Development - Chapter 13 : 접촉 발생 (0) | 2018.12.02 |
Fast 3D Triangle-Box overlap Testing (0) | 2018.11.28 |
Sphere triangle test & AABB Triangle intersection (0) | 2018.11.28 |