우리의 물리 시스템의 최종, 가장 복잡한, 단계를 볼 시간이다. 우리는collision detector (from chapter 13)의 contact data의 한 집합을 가지게 되었고, 그리고 우리는 torques and forces를 포함한 (from chapter 10), 움직임의 rigid-body equations를 갖는다. 우리는 이제 그 두 개를 결합할 준비가 되었고, 회전하는 오브젝트들을 contacts에 반응하도록 할 준비가 되었다.
chapter 7에서 처럼, 우리는 처음에 충돌의 물리학에 상세하게 볼 것이다. 우리는 micro-collision physics engine을 구성할 것인데, resting contacts가 수 많은 mini-collisions로 처리되는 것이다 (추가적인 특별한 목적의 코드를 더해서). 우리가 resting contacts가 작동하는 것의 micro-collisions에 가기 전에, 우리는 기본 collision handling을 상세히 볼 필요가 있다.
이 챕터는 충돌을 처리하기 위해 우리의 contact resolution system의 첫 번째 단계를 구성한다. Chapter 15는 충돌 반응을 좀 더 일반적이고 튼튼한 contact handling으로 통합하기 위해 진행한다.
엔진에서 모든 contact handling은 충돌을 기반으로 하기 때문에, 책 페이지, 수학적 복잡성, 구현의 어려움의 관점에서, 이 챕터는 우리의 엔진을 마무리하는 가장 큰 파트를 차지한다. 만약 너가 이 챕터가 어렵게 간다는 것을 알게 된다면, 그러면 이내하려고 노력해라: 여기서부터는 거의 내리막길이다.
{
여기까지도 충분히 힘들었는데, 저자는 여기가 가장 어려운 부분이라 말하니,, 얼마나 어려울지 감이 안잡힌다......
}
14.1 Impulses And Impulsive Torques
실제 세계에서 두 오브젝트 사이에 한 충돌이 발생할 때, 그것들이 만들어진 재료가 다소 응축된다는 것을 회상해라. 그것이 고무 공 또는 돌이든지, 충돌 지점 근처의 분자들은 아주 미세하기 서로 눌려진다. 그것들이 압축될 때, 그것들은 그것들의 원래 모양으로 돌아오려 하는 한 힘을 발휘한다.
다른 오브젝트들은 변형되는 것에 다른 저항을 가지고, 그것들의 원래 모양으로 돌아오는 것에 다른 경향성을 가진다. 결합된 두 개의 경향은 한 오브젝트에게 그것의 특정한 bounce를 준다. 고무 공은 쉽게 변형될 수 있지만, 그것의 원래 모야으로 돌아오려는 높은 경향성을 가진다. 그래서 그것은 잘 튄다. 한 돌은 그것의 원래 모양으로 돌아오려는 높은 경향성을 가지지만, 변형되는 것에 높은 저항성을 가진다: 그것은 튈 것이지만, 그렇게 많이는 아니다. 한 줌의 흙은 변형되는 것에 낮은 저항성을 가질 것이지만, 그것의 원래 모양으로 돌아오려는 경향성이 없다: 그것은 전혀 튀지 않을 것이다.
변형을 거부하려는 힘은 그 오브젝트가 충돌하는 것을 멈추게 한다: 그것들의 속도는 그것들이 더 이상 함께 움직이지 않을 때 까지 줄어든다. 이 점에서, 그 오브젝트들은 최대로 응축된다. 만약 그것들의 원래 모양으로 돌아가려는 경향이 있다면, 그러면 그 힘은 그것들이 더 이상 변형이 되지 않을 때 까지 가속하기 시작할 것이다. 이 지점에서, 오브젝트들은 일반적으로 떨어져서 움직인다.
이 모든 것은 일초의 아주 작은 부분에서 발생한다, 그리고 합리적으로 뻣뻣한 오브젝트들에 대해 (두 개의 당구공 같은), 그 응축 거리는 1 밀리미터의 아주 작은 부분이다. 거의 모든 경우에, 그 변형은 눈으로는 보여질 수 없다; 그것은 너무 작고, 너무 빠르다. 우리의 관점으로부터, 우리는 간단히 그 두 개의 오브젝트들이 충돌하고 즉시 튀어오르는 것을 본다.
나는 미세한 수준에서 발생하고 있는 것을 강조한다. 왜냐하면 그것은 수학을 좀 더 논리적으로 만들기 때문이다. 그러나, 우리가 상세하게 bounce를 재현하는 것은 실용적이지 않다. 응축력은 너무 뻣뻣하고, 우리가 stiff springs로 봄에 따라, 그 결과는 재앙이 될 것이다.
chapter 7에서, 우리는 두 개의 point objects들이 충돌 전에 그것들의 closing velocity의 고정된 배수의 속도로 떨어질 것을 보았었따. 이것을 재현하기 위해, 우리는 즉시 충돌에서 각 오브젝트의 속도를 바꾼다. 속도에서의 변화는 "impulse, 충격"이라고 불려진다.
14.1.1 Impulsive Torque
우리가 회전하는 rigid bodies를 다루기 때문에, 상황은 좀 더 복잡하다. 만약 너가 땅에서 회전하고 있는 한 오브젝트를 튕긴다면, 너는 그 오브젝트가 위로 움직이기 시작할 뿐만 아니라, 그것의 angular velocity 또한 보통 변할 것을 눈치챌 것이다.
chapter 7의 충돌 방정식을 적용하는 것은 충분하지 않다. 왜냐하면 그것들은 linear motion만을 고려하기 때문이다. 우리는 충들 효과가 linear and angular velocities 둘 다에 어떻게 영향을 미치는지 이해할 필요가 있다.
그림 14.1은 땅으로 회전되는 긴 막대를 보여준다 (우리는 잠시 뒤에 두 개의 움직이는 오브젝트 사이의 충돌로 돌아갈 것이다). 실제 세계에서 충돌의 순간에 무엇이 발생하는지를 상세히 봐보자. 그림의 두 번째 부분의 그 오브젝트의 충돌 순간의 변형을 보여준다. 이것은 보여지는 방향으로 응축력이 누르게 한다.
chapter 10에서 D'Alembert의 원리를 보고, 우리는 한 오브젝트에 작용하는 어떤 힘은 linear and angular acceleration 둘 다를 생성한다는 것을 보았었다. 그 linear component는
다음에 의해 주어진다.
그리고 torque에 의해 angular component는
여기에서 그 torque는 angular acceleration을 다음으로 생성한다
이것은 chapter 10의 10.3 방정식이다.
충돌의 경우에, 그 충돌이 속도에서의 linear change (the impulse)를 생성하고, 속도에서 angular change를 생성할 것이라고 추론하는 것이 나타나게 된다. 속도에서의 즉각적인 angular change는 "impulsive torque"라고 불려진다 (또한 "moment of impulse" or "impulsive moment"라고 잘 불려지지 않는데," 그것은 나에게는 drunken Vegas wedding 처럼 들린다).
우리가 가졌던것과 같은 방식으로
torques에 대해 우리는 다음을 가진다
여기에서 u는 impulsive torque이고, I는 inertia tensor이고, \dot{\theta}는 angular velocity이다 이전처럼. 이것은 방정식 7.5과 같은 것인데, 그것은 linear impulses를 다뤘었다. 그리고 그에 따라, angular velocity에서의 변화인 delta(dot(theta))는
모든 이러한 방정식에서, I는 월드 좌표계에 있다, 섹션 10.2.3에서 이야기 되었듯이.
Impulses는 forces처럼 행동한다. 특히 주어진 impulse에 대해, linear component와 angular component 둘 다 있다. torque의 양이 다음 처럼 주어지듯이,
그래서, 한 impulse에 의해 생성되는 impulsive torque는 다음으로 주어진다
우리의 경우에, 충돌에 대해서, 적용점(p_f)는 접촉점과 그 오브젝트의 원점에 의해 주어진다:
여기에서 q는 contact의 월드 좌표에서의 position이고, p는 object의 월드 좌표계의 원점이다.
14.1.2 Rotating Collisions
충돌에서 impulse의 효과는 각 오브젝트들의 점들이 충돌할 때 튕견 ㅏ가게 하는 것이다. 충돌점에서 충돌하는 오브젝트들의 움직임은 여전히 우리가 chapter 7에서 만났던 같은 방정식을 따른다. 다시 말해서, 만약 우리가 충돌 시간에서 두 개의 충돌점 (서로로부터 하나씩)을 추적한다면, 우리는 그것들의 separating velocity가 다음으로 주어지는 것을 본다:
여기에서 v_s는 충돌 직전의 그 오브젝트의 relative velocity이고, v_s'는 충돌 후의 relative velocity이고, c는 restitution 계수이다. 다시 말해서, 그 separating velocity는 항상 closing velocity와 반대 방향에 있고, 그것의 크기와 상수 비율이다. 그 contact c는 관련된 두 오브젝트의 물질에 의존한다.
관련된 오브젝트의 특징과 contact normal의 방향에 따라, 이 separation velocity는 linear and rotational motion의 다른 degree로 구성될 것이다. 그림 14.2는 같은 충돌에 포함된 다른 오브젝트들을 보여준다 (또다시 명확성을 위해 움직이지 않는 땅과 함께 그려진다). 그림의 각 부분에서, 접촉점에서 closing velocity와 복구계수는 같고, 그래서 separating velocity 또한 같다.
첫 번째 오브젝트는 가볍고, 거의 정면으로 충돌하고 있따. 충돌하는 동안 생성되는 어떤 force에 대해, 그 대응되는 torque는 작은 것이다. 왜냐하면 f가 거의 p_f에 평행하기 때문이다. 그것의 bounce는 거의 linear일 것이고, 작은 회전 component를 가질 거싱다.
두 번째 오브젝트는 더 무거지만, Z축에 대해 작은 moment of inertia를 가지고 있다. 여기에서 생성된 torque는 클 것이고, 관성모멘트는 매우 작기 때문에, 큰 회전 response가 있을 것이다. 그 회전 반응은 사실 너무 커서, linear component는 그 오브젝트를 위로 튕길만큼 충분히 크지 않다. 비록 접촉 점이 위로 튀어오를지라도 (각 다른 경우에 접촉점으로서 같은 속도로), 오브젝트의 회전은 대개 separaing하고 있고, 그래서 linear motion은 계속해서 다소 더 낮은 비율로 아래쪽으로 내려간다. 너는 만약 그림 14.2에서 보여지는 설정으로 땅에 자를 떨어뜨린다면 이것을 관찰할 수 있다. 그 자는 접촉점으로부터 급격히 회전하기 시작할 것이지만, 전체적으로 공중으로 튀어오르지 않을 것이다. 그 회전은 접촉점을 분리하는 데에 대부분의 책임이 있다.
그림에서 그 세 번째 오브젝트는 두 번째와 같은 방식으로 충돌한다. 그러나, 이 경우에, 비록 질량이 같을지라도, 그것의 관성모멘트는 더욱 크다. 그것은 그것의 양 극단 부분에 더 많은 질량을 가진 오브젝트를 나타낸다. 여기에서 그 compression force는 더 낮은 양의 회전을 야기시킨다. 그 linear impulse는 더 크고, impulsive torque는 더 작다. 그 오브젝트는 linearly하게 튀어오르고, compression force는 회전의 방향과 반대가 되지만, 최종 angular velocity 는 매우 작다.
14.1.3 Handling Rotating Collisions
파티클 충돌에 대해서 처럼, 우리는 우리의 충돌 반응에 대해 두 가지 부분이 필요하다. 첫 째로, 우리는 두 오브젝트들의 relative motion을 impulse와 impulsive torque를 적용해서 resolve할 필요가 있다. 우리가 속도에 대해 한 충돌을 처리할 때, 우리는 4개의 값을 계산할 필요가 있다: the impulse and impulsive torque for both objects in the collision. 적용할 linear and angular impulse의 balance를 계산하는 것은 복잡한 일이고, 몇 가지 복잡한 수학을 포함한다. 우리가 다음 섹션에서 보게 되듯이.
우리는 오직 각 프레임의 끝에서 충돌을 체크하기 때문에, 오브젝트들은 이미 서로를 통과했을지도 모른다. 그래서 둘 째로, 우리는 발생한 어떤 관통을 해결할 필요가 있다. 그 관통은 파티클에 대해 관통과 유사한 방법으로 처리될 수 있다. 그러나 어쨋든 우리가 할 필요가 있는 impulse calculations은 우리가 좀 더 물리적으로 현실적인 관통 resolution을 유도하도록 허용한다. 우리는 이 프로세스를 14.3에서 돌아갈 것이다.
14.2 Collision Impulses
두 오브젝트들의 relative motion을 resolve하기 위해, 우리는 4개의 impulses를 계산할 필요가 있다: 각 오브젝트에 대한 linear and angular impulses. 만약 그 충돌에 관련된 오직 한 오브젝트만 있다면 (만약 한 오브젝트가 땅과 같은 움직일 수 없는 오브젝트와 충돌한다면), 그러면 우리는 두 개의 값만이 필요하다 : 그 단일 오브젝트에 대한 impulse와 impulsive torque.
각 오브젝트에 대한 impulse와 impulsive force를 계산하기 위해서, 우리는 일련의 단계들을 거친다:
- 우리는 contact에 상대적인 좌표들의 한 집합에서 작업한다. 이것은 대부분의 수학을 훨씬 더 간단하게 만든다. 우리는 이러한 좌표들의 새로운 집합에서 변환시킬 transformation matrix를 만든다.
- 우리는 unit impulse당 각 오브젝트에서의 접촉점의 속도에서 변화를 처리한다. impulse가 linear and angular motion을 야기시키기 때문에, 이 값은 두 개의 컴포넌트들을 고려할 필요가 있다.
- 우리는 (다음 단계에서) 우리가 보길 원하는 velocity change을 알 것이고, 그래서 우리는 어떤 주어진 velocity change를 생성하는데 필요한 impulse를 찾기위해 마지막 단계의 결과를 invert 시킨다.
- 우리는 접촉점에서 separating velocity가 정확히 무엇이 되어야 하는지, closing velocity가 현재 무엇인지, 그리고 그 둘 사이의 차이가 무엇인지를 처리한다. 이것은 속도에서 바라는 변화이다.
- 속도에서 바라는 변화로부터, 우리는 생성되어야 할 impulse를 계산할 수 있다.
- 우리는 그 impulse를 그것의 linear and angular components로 분해하고, 그것들을 각 오브젝트에 적용한다.
이러한 단계뜰을 차례로 봐보자.
14.2.1 Change to Contact Coordinates
우리의 목표는 우리가 충돌의 결과로서 적용할 필요가 있는 impulse를 처리하는 것이다. 그 impulse는 속도에서의 변화를 발생시킬 것이고, 우리는 우리가 찾는 속도에서의 변화를 발생시킬 impulse를 찾을 필요가 있다.
우리는 이 단계에서 전체 오브젝트의 linear and angular velocity에 관심이 없다. 충돌의 목적을 위해서, 우리는 오직 contact points의 separating velocity에만 관심이 있다. 우리가 이전 섹션에서 보았듯이, 우리는 우리에게 그 최종 separating velocity가 무엇이 될 필요가 있는지를 말해주는 방정식을 가지고 있고, 그래서 우리는 그것을 간단히 적용할 수 있고 싶어한다.
한 오브젝트에서 한 점의 속도는 그것의 linear and angular velocity와 관련이 있다, 방정식 9.5에 따라:
우리는 이 단계에서 충돌 점의 움직임에만 관심이 있기 때문에, 우리는 충돌점을 기준으로 계싼을 하여 그 수학을 간단하게 할 수 있다.
chapter 13로부터 각 contact가 관련된 contact point와 contact normal를 갖는다는 것을 회상해라. 만약 우리가 이 점을 원점으로 사용하고, 그 contact normal를 한 축으로서 사용한다면, 우리는 그것을 둘러싸는 orthonormal basis를 형성할 수 있다: 세 개의 축들의 집합. 우리가 월드 좌표계를 가지고, 각 오브젝트에 대해 local 좌표계를 가진 것처럼, 우리는 각 contact에 대해 contact 좌표계를 가질 것이다.
그림 14.3은 한 contact에 대한 contact 좌표를 보여준다. 우리가 이 단계에서 관통은 무시한다는 것에 유의해라. contact generation의 일부로서, 우리는 각 contact에 대한 단일이ㅡ 대표점을 계산했다.
The Contact Coordinate Axes
contact coordinates로 변환시키는 첫 단계는 각 축의 방향을 처리하는 것이다. 우리는 section 2.1.9에서 보여졌듯이, orthonormal basis를 계산하는 알고리즘을 사용하여 이것을 한다. 그 X축은 우리가 이미 알고 있다: 그것은 collision detector에 의해 생성된 contact normal이다. 그 Y축과 Z축은 계산되어질 필요가 있다.
불행하게도, 하나의 X축으로부터 생성되는 다른 Y and Z 축들이 있을 수 있다. 우리는 하나를 고를 필요가 있을 것이다. 만약 우리가 anisotropic friction으로 작업한다면 (다른 방향에서는 다른 마찰), 그러면 가장 적합한 기저 벡터들의 한 집합이 있을 것이다. 이 책에서 isotropic friction과, firctionless simulations에 대해, 어떤 집합은 동일하게 유효하다. 우리는 지금은 friction을 무시하기 때문에, 우리는 Y축이 world Y axis를 가리키도록 하여 시작하여서 임의의 축들의 집합을 만든다 (우리의 경우에 그 알고리즘이 base axis인 X axis와 추가로 두 번째 축에서 초기 추측을 요구하는 것을 회상해라, 그리고 이것은 결국에 변경되어질 것이다):
이것은 section 2.1.9에 주어진 알고리즘을 따른다. 그 Y축 가정은 그 함수가 호출될 때 제공된다:
이 알고리즘은 x와 y벡터가 평행할 때 처리할 수 없다. 우리는 쉽게 그것을 하도록 그 알고리즘을 수정할 수 있다, zero에 대한 체크가 실패할 때 반환하기 보다. 우리는 다른 y값을 사용할 수 있고 (예를들어, 그것의 0이 아닌 요소중에서 두 개를 바꾸거나 또는 한 개를 inverting하여), 그리고 z를 재 계산한다. 우리는 이 문제에 나중에 돌아올 것이다. 그 계산 코드 자체에 몇 가지 개선을 한 후에.
우리는 그 벡터곱을 수동으로 수행하여 이 긴 형태의 효율성을 개선할 수 있다 (Vector3 class에서 vector product operator를 호출하기 보다는). 처음에 초기 Y축이 Y축을 따라 가리킨다면, 그러면 최종 Z축이 Y축에 직각이라는 것을 유의해라. 이것은 만약 최종 Z축이 0의 Y component를 가진다면 발생할 수 있다.
둘 째로, 끝에서 벡터를 표준화하기 보다는, 그것들이 진행함에 따라 표준화되는 것을 보장할 수 있다. 우리는 Z축에 대한 계산이 표준화된 벡터로 끝나는것을 보장하여 이것을 한다. 왜냐하면 벡터곱은 다음의 방정식을 따르기 때문에
우리는 X와 Z축이 직각이라는 것을 안다 (sintheta = 1) 그리고 둘 다 표준화 되어있다는 것을 안다 (|z| = |x| = 1) 그러고나서, 최종 Y 축은 1의 크기를 가져야 한다 :그것의 표준화 되어있다. 그 짧은 코드는 이렇게 보인다:
{
내 생각에는, orthonormal basis in contact coordinates를 계산하는데 코드에 문제가 있는 것 같다. 외적을 manual calculation으로 곱하고 있는데, 저자가 잘못 계산하지 않았나 싶다. 그러나 진행을 해야하기 때문에, 일단은 내 코드로 가고 무언가 이상하다면 저자의 코드로 바꾼다. 일단은 계쏙 가자.
}
내 생각에는, orthonormal basis in contact coordinates를 계산하는데 코드에 문제가 있는 것 같다. 외적을 manual calculation으로 곱하고 있는데, 저자가 잘못 계산하지 않았나 싶다. 그러나 진행을 해야하기 때문에, 일단은 내 코드로 가고 무언가 이상하다면 저자의 코드로 바꾼다. 일단은 계쏙 가자.
}
다뤄야 할 한 가지 문제가 있다. 만약 넘겨진 (그 X축으로서) contact normal이 이미 world-space Y축의 방향으로 가리키고 있다면, 그러면 우리는 Z 축의 모든 컴포넌트들이 0으로 끝나게 될 것이다. 이 경우에, world-space Y-axis를 사용하는 것은 좋은 추축이 아니다; 우리는 다른 것을 사용할 필요가 있다. 우리는 world-space X axis or Z axis 둘 중 하나를 사용할 수 있다. 내가 구현한 코드는 world-space X axis를 사용한다.
가능한한 알고리즘을 안정적으로 만들기 위해서, 우리는 world-space Yaxis와 X axis를 사용하는 것을 번갈아서 하는 것을 최상의 guess로 하여 가능한한 안정적으로 만든다. 그리고 이것은 어떤 contact normal 이 가장 가까이에 있는지에 따른다. 우리는 그 contact normal이 정확히 Y축의 방향에 있을 때 바꾸지만, 만약 그것이 그 ㅂ아향에 가깝다면, 우리는 numerical problems와 부정확한 결과를 얻게 될 것이다.
The Basis Matrix
가능한한 알고리즘을 안정적으로 만들기 위해서, 우리는 world-space Yaxis와 X axis를 사용하는 것을 번갈아서 하는 것을 최상의 guess로 하여 가능한한 안정적으로 만든다. 그리고 이것은 어떤 contact normal 이 가장 가까이에 있는지에 따른다. 우리는 그 contact normal이 정확히 Y축의 방향에 있을 때 바꾸지만, 만약 그것이 그 ㅂ아향에 가깝다면, 우리는 numerical problems와 부정확한 결과를 얻게 될 것이다.
The Basis Matrix
기저를 계산하는 완전한 코드를 보기전에, 우리는 그것이 무엇을 만들어 낼지를 다시 볼 필요가 있다. 지금까지, 나는 우리가 orthonormal basis를 구성하는 세 개의 벡터로 끝날 것이라고 가정했다.
세 개의 벡터의 한 집합보다는 한 행렬로 작업하는 것이 좀 더 편리하다. section 9.4.2에서, 한 행렬이 축들의 한 집합에서 다른 것으로 넘어가는 transformation으로서 생각되어질 수 있는 것을 고려해라.
이 섹션의 몇 가지 지점에서, 우리는 contact axes("contact coordinates" or "contact space"라고 불려지는)와 world space사이에서 convert를 할 필요가 있을 것이다. 이것을 하기 위해서, 우리는 그 변환을 수행하는 한 행렬이 필요하다.
우리가 섹션 9.4.2에서, local space into world space의 transformation matrix는 그 세 개의 local-space axes를 행렬의 columns으로 배치하여 구성될 수 있었다. 그래서, 만약 우리가 세 개의 벡터를 구성하는 orthonormal basis를 갖는다면
우리는 그것들을 하나의 transformation matrix로 합칠 수 있다.
만약 우리가 local space에서 표현된 한 좌표의 집합을 가지고 있고, 우리가 같은 점의 좌표를 world space에서 원한다면, 우리는 그 transformation matrix를 간단히 좌표벡터에 곱한다:
Mp_local = p_world
다시 말해서, 그 기저 행렬은 local coordinates를 world coordinates로 변환한다.
우리는 이것을 코드에 합칠 수 있다. 이 함수는 orthonormal axes의 한 집합을 만들기 위해 contact normal에 작동하고, 그러고나서 그 contact coordinate scheme을 나타내는 기저 행렬을 만들어낸다. 그 행렬은 contact coordinates를 world coordinates로 변환하는 transformation으로서 작동할 수 있다:
{
즉, 세 개의 contact coordinates를 구했고, 그것을 column으로 설정하는 것은, 선형대수에서, contact coordinates의 기저가 world coordinates으로 표현된 것이므로,
contact space -> world space로 변환시키는 기저변환 행렬이 되는 것이다.
그러므로, 이것을 전치시킨것은, contact coordinates를 row에 설정하는 것이고, world space에서 contact space에서 바꾸는 것이다.
}
세 개의 벡터의 한 집합보다는 한 행렬로 작업하는 것이 좀 더 편리하다. section 9.4.2에서, 한 행렬이 축들의 한 집합에서 다른 것으로 넘어가는 transformation으로서 생각되어질 수 있는 것을 고려해라.
이 섹션의 몇 가지 지점에서, 우리는 contact axes("contact coordinates" or "contact space"라고 불려지는)와 world space사이에서 convert를 할 필요가 있을 것이다. 이것을 하기 위해서, 우리는 그 변환을 수행하는 한 행렬이 필요하다.
우리가 섹션 9.4.2에서, local space into world space의 transformation matrix는 그 세 개의 local-space axes를 행렬의 columns으로 배치하여 구성될 수 있었다. 그래서, 만약 우리가 세 개의 벡터를 구성하는 orthonormal basis를 갖는다면
우리는 그것들을 하나의 transformation matrix로 합칠 수 있다.
만약 우리가 local space에서 표현된 한 좌표의 집합을 가지고 있고, 우리가 같은 점의 좌표를 world space에서 원한다면, 우리는 그 transformation matrix를 간단히 좌표벡터에 곱한다:
Mp_local = p_world
다시 말해서, 그 기저 행렬은 local coordinates를 world coordinates로 변환한다.
우리는 이것을 코드에 합칠 수 있다. 이 함수는 orthonormal axes의 한 집합을 만들기 위해 contact normal에 작동하고, 그러고나서 그 contact coordinate scheme을 나타내는 기저 행렬을 만들어낸다. 그 행렬은 contact coordinates를 world coordinates로 변환하는 transformation으로서 작동할 수 있다:
{
즉, 세 개의 contact coordinates를 구했고, 그것을 column으로 설정하는 것은, 선형대수에서, contact coordinates의 기저가 world coordinates으로 표현된 것이므로,
contact space -> world space로 변환시키는 기저변환 행렬이 되는 것이다.
그러므로, 이것을 전치시킨것은, contact coordinates를 row에 설정하는 것이고, world space에서 contact space에서 바꾸는 것이다.
}
Matrix3 class의 setComponents 메소드가 행렬에서 columns을 설정한다. 그것은 다음처럼 구현된다.
나의 calculateContactBasis() 코드:
나의 calculateContactBasis() 코드:
/** * Constructs an arbitrary orthonormal basis for the contact. This is * stored as a 3x3 matrix, where each vector is a column (in other * words the matrix transforms contact space into world space). The x * direction is generated from the contact normal, and the y and z * directions are set so they are right angles to it. */ /** * The modifed Version by ChanHaneg * Refer to the issue of github which I uploaded * https://github.com/idmillington/cyclone-physics/issues/49 */ inline void GPED::Contact::calculateContactBasis() { glm::vec3 contactTangent[2]; // Scaling factor to ensure the results are normalised const real s = glm::length(contactNormal); // Check whether the X-axis is nearer to the X or Y axis if (real_abs(contactNormal.x) > real_abs(contactNormal.y)) { // The new Z-axis is at right angles to the world Y-axis contactTangent[0].x = contactNormal.z * s; contactTangent[0].y = 0; contactTangent[0].z = -contactNormal.x * s; // The new Y-axis is at right angles to the new X- and Z-axes contactTangent[1].x = -contactNormal.y * contactTangent[0].z; contactTangent[1].y = contactNormal.z * contactTangent[0].x - contactNormal.x * contactTangent[0].z; contactTangent[1].z = contactNormal.y * contactTangent[0].x; // Make a matrix from the three vectors contactToWorld[0] = contactNormal; contactToWorld[1] = contactTangent[0]; contactToWorld[2] = contactTangent[1]; } else { /** * Contact Normal becomes Y-axis. * The Z-axis is calculated by crossProduct (World X-axis, Y-axis) * The New X-axis is calculated by crossProduct (Y-axis, New Z-axis) * These lines are also different from the original code. * I think the calculation of the original code is wrong. */ // The new Z-axis is at right angles to the world X-axis contactTangent[0].x = 0; contactTangent[0].y = -contactNormal.z * s; contactTangent[0].z = -contactNormal.y * s; // The new X-axis is at right angles to the new X- and Z-axes contactTangent[1].x = contactNormal.y * contactTangent[0].z - contactNormal.z * contactTangent[0].y; contactTangent[1].y = contactNormal.x * contactTangent[0].z; contactTangent[1].z = contactNormal.x * contactTangent[0].y; contactToWorld[0] = contactTangent[1]; contactToWorld[1] = contactNormal; contactToWorld[2] = contactTangent[0]; } }
The Inverse Transformation
여기에서 섹션 9.4.3에서 보았던 결과의 개요를 말하는 것은 가치가 있다- 이름 그대로, 한 회전 행렬의 역은 그것의 전치와 같다는 것이다. 왜 우리는 그 역에 관ㅅ미이 있는가? 왜냐하면 contact coordinates에서 world coordinates로 변환하는 것외에도, 우리는 그 반대로 또한 가야할지도 모르기 때문이다.
world coordinates에서 contact coordinates로 변환하기 위해서, 우리는 이전 코드에서 만들어진 basis matrix의 역을 사용한다. 일반적으로 한 행렬을 역으로 하는 것은, 우리가 보았듯이, 복잡하다. 운 좋게도, 우리가 basis matrix을 정의 했을 때 그 basis matrix가 오직 회전만을 나타낸다: 그것이 3x3 행렬이라는 것이고, 그래서 그것은 평행이동 컴포넌트를 가질 수 없다; 그리고 그 contact axes와 world axes 둘 다 orthonormal이기 때문에, 관련된 어떠한 skewing과 scaling도 없다.
이것은 우리가 world coordinates into contact coordinates로의 변환을 기저 행렬의 전치를 사용해서 수행할 수 있다는 것을 의미한다:
이 중요한 결과는 우리가 마음대로 contact coordinates와 world coordinates 사이를 변환하게 허용해준다.
몇 가지 계산이 쉬울 때마다, 우리는 그것들 사이를 간단히 변환할 수 있다. 우리는 다음 섹션에서 이 중요한 결과를 사용할 것이고, 다음 챕터에서는 더 많이 사용할 것이다.
14.2.2 Velocity Change By Impulse
두 오브젝트들의 충돌때의 움직임에서 변화가 compression과 deformation에 의해 충돌점에서 생성된 forces에 야기된다는 것을 기억해라. 우리가 전체 충돌 사건을 시간에서 single moment로 나타내기 때문에, 우리는 forces라기보다는 impulses를 사용한다. Impulses는 속도에서의 변화를 야기시킨다 (angular and linear 둘 다, D'Alembert의 원칙에 따라, forces처럼).
그래서, 만약 우리의 목표가 충돌시에 impulse를 계산하는 것이라면, 우리는 한 impulse가 각 오브젝트에 무슨 영향을 갖는지를 이해할 필요가 있다. 우리는 어떤 주어진 충격에 대해 각 오브젝트의 속도 변화가 무엇이 될 것인지를 말해주는 수학적 구조를 얻게 되길 원한다.
우리가 이 챕터에서 고려하고 있는 마찰이 없는 contacts에 대해, contact에서 생성되는 유일한 impulses는 contact normal를 따라 적용된다. 우리는 contact에서 contact normal의 방향으로 속도의 변화를 말해주는, 같은 방향으로 적용되는 impulse의 each unit에 대해 간단한 숫자를 갖길 원한다.
우리가 보았듯이, unit impulse당 velocity change는 두 가지 컴포넌트들을 갖는다: a linear component and an angular component. 우리는 이러한 것들을 개별적으로 다룰 수 있고, 끝에서 그것들을 합칠 수 있다.
또한 그 값이 두 개의 bodies에 의존한다는 것을 주목하는 것은 가치가 있다. 우리는 충돌과 관련된 각 오브젝트에 대해 linear and angular velocity change를 발견할 필요가 있을 것이다.
The Linear Component
그 linear component는 매우 간단하다. 단위 충격에 대한 속도에서의 linear change는 그 충격의 방향에 있을 것이다, inverse mass에 의해 주어진 크기와 함께:

두 개의 오브젝트가 관련된 충돌에 대해서, 그 linear component는 간단히 두 개의 inverse mass의 합이다:

이 방정식이 속도의 linear component에만 유효하다는 것을 기억해라 - 그것들은 아직 완전한 그림이 아니다!
The Angular Component
그 angular component는 좀 더 복잡하다. 우리는 우리가 책에서 다양한 지점에서 만났던 세 개의 방정식을 가져올 필요가 있다. 편리함을 위해서, 한 오브젝트의 원점을 기준으로 하는 contact의 position을 q_rel으로 사용할 것이다:

처음에, 방정식 14.2는 우리에게 단위 충격으로부터 생성된 impulsive torque의 양을 말해준다:

여기에서 d는 충격의 방향이다 (우리의 경우에 contact normal).
둘 쨰로, 방정식 14.1은 우리에게 단위 impulsive torque에 대해 angular velocity의 변화를 말해준다:

그리고 마지막으로, 방정식 9.5는 우리에게 한 점의 total velocity에 대해 말해준다. 만약 우리가 linear component를 제거한다면, 우리는 그것의 회전에 의해서만 한 점의 linear velocity에 대한 방정식을 얻는다:

여기에서 섹션 9.4.3에서 보았던 결과의 개요를 말하는 것은 가치가 있다- 이름 그대로, 한 회전 행렬의 역은 그것의 전치와 같다는 것이다. 왜 우리는 그 역에 관ㅅ미이 있는가? 왜냐하면 contact coordinates에서 world coordinates로 변환하는 것외에도, 우리는 그 반대로 또한 가야할지도 모르기 때문이다.
world coordinates에서 contact coordinates로 변환하기 위해서, 우리는 이전 코드에서 만들어진 basis matrix의 역을 사용한다. 일반적으로 한 행렬을 역으로 하는 것은, 우리가 보았듯이, 복잡하다. 운 좋게도, 우리가 basis matrix을 정의 했을 때 그 basis matrix가 오직 회전만을 나타낸다: 그것이 3x3 행렬이라는 것이고, 그래서 그것은 평행이동 컴포넌트를 가질 수 없다; 그리고 그 contact axes와 world axes 둘 다 orthonormal이기 때문에, 관련된 어떠한 skewing과 scaling도 없다.
이것은 우리가 world coordinates into contact coordinates로의 변환을 기저 행렬의 전치를 사용해서 수행할 수 있다는 것을 의미한다:
이 중요한 결과는 우리가 마음대로 contact coordinates와 world coordinates 사이를 변환하게 허용해준다.
몇 가지 계산이 쉬울 때마다, 우리는 그것들 사이를 간단히 변환할 수 있다. 우리는 다음 섹션에서 이 중요한 결과를 사용할 것이고, 다음 챕터에서는 더 많이 사용할 것이다.
14.2.2 Velocity Change By Impulse
두 오브젝트들의 충돌때의 움직임에서 변화가 compression과 deformation에 의해 충돌점에서 생성된 forces에 야기된다는 것을 기억해라. 우리가 전체 충돌 사건을 시간에서 single moment로 나타내기 때문에, 우리는 forces라기보다는 impulses를 사용한다. Impulses는 속도에서의 변화를 야기시킨다 (angular and linear 둘 다, D'Alembert의 원칙에 따라, forces처럼).
그래서, 만약 우리의 목표가 충돌시에 impulse를 계산하는 것이라면, 우리는 한 impulse가 각 오브젝트에 무슨 영향을 갖는지를 이해할 필요가 있다. 우리는 어떤 주어진 충격에 대해 각 오브젝트의 속도 변화가 무엇이 될 것인지를 말해주는 수학적 구조를 얻게 되길 원한다.
우리가 이 챕터에서 고려하고 있는 마찰이 없는 contacts에 대해, contact에서 생성되는 유일한 impulses는 contact normal를 따라 적용된다. 우리는 contact에서 contact normal의 방향으로 속도의 변화를 말해주는, 같은 방향으로 적용되는 impulse의 each unit에 대해 간단한 숫자를 갖길 원한다.
우리가 보았듯이, unit impulse당 velocity change는 두 가지 컴포넌트들을 갖는다: a linear component and an angular component. 우리는 이러한 것들을 개별적으로 다룰 수 있고, 끝에서 그것들을 합칠 수 있다.
또한 그 값이 두 개의 bodies에 의존한다는 것을 주목하는 것은 가치가 있다. 우리는 충돌과 관련된 각 오브젝트에 대해 linear and angular velocity change를 발견할 필요가 있을 것이다.
The Linear Component
그 linear component는 매우 간단하다. 단위 충격에 대한 속도에서의 linear change는 그 충격의 방향에 있을 것이다, inverse mass에 의해 주어진 크기와 함께:
두 개의 오브젝트가 관련된 충돌에 대해서, 그 linear component는 간단히 두 개의 inverse mass의 합이다:
이 방정식이 속도의 linear component에만 유효하다는 것을 기억해라 - 그것들은 아직 완전한 그림이 아니다!
The Angular Component
그 angular component는 좀 더 복잡하다. 우리는 우리가 책에서 다양한 지점에서 만났던 세 개의 방정식을 가져올 필요가 있다. 편리함을 위해서, 한 오브젝트의 원점을 기준으로 하는 contact의 position을 q_rel으로 사용할 것이다:
처음에, 방정식 14.2는 우리에게 단위 충격으로부터 생성된 impulsive torque의 양을 말해준다:
여기에서 d는 충격의 방향이다 (우리의 경우에 contact normal).
둘 쨰로, 방정식 14.1은 우리에게 단위 impulsive torque에 대해 angular velocity의 변화를 말해준다:
그리고 마지막으로, 방정식 9.5는 우리에게 한 점의 total velocity에 대해 말해준다. 만약 우리가 linear component를 제거한다면, 우리는 그것의 회전에 의해서만 한 점의 linear velocity에 대한 방정식을 얻는다:
한 점 (dot(q))의 회전에 의해 유발된 속도는 object의 원점을 기준으로한 포지션 (q - p)에 의존하고, 오브젝트의 angular velocity에 의존한다.
그래서 우리는 이제 단위 impulse로부터 우리가 얻을 수 있는 방정식의 한 집합을 가졌다, 그것이 발생시키는 impulsive torque와 그 torque가 발생시키는 angular velocity를 통해서, 그리고 결과가되는 linear velocity를 통해서.
이러한 세 개의 방정식을 코드로 변환하여, 우리는 다음을 얻는다:
그래서 우리는 이제 단위 impulse로부터 우리가 얻을 수 있는 방정식의 한 집합을 가졌다, 그것이 발생시키는 impulsive torque와 그 torque가 발생시키는 angular velocity를 통해서, 그리고 결과가되는 linear velocity를 통해서.
이러한 세 개의 방정식을 코드로 변환하여, 우리는 다음을 얻는다:
glm::vec3 torquePerUnitImpulse = glm::cross(relativeContactPosition, contactNormal); glm::vec3 rotationPerUnitImpulse = inverseInertiaTensor * torquePerUnitImpulse; glm::vec3 velocityPerUnitImpulse = glm::cross(rotationPerUnitImpulse, relativeContactPosition);
그 결과는 단위 충격당 회전에 의해 야기되는 속도가 될 것이다. 그것이 나타내듯이, 그 결과는 한 벡터이다: 그것은 world space에서의 한 속도이다. 우리는 오직 contact normal를 따르는 속도에만 관심이 있다.
우리는 우리가 이전에 보았던 transpose basis matrix를 사용하여 이 벡터를 contact coordinates로 변환할 필요가 있다. 이것은 우리에게 단위 충격이 발생시킬 속도의 한 벡터를 줄 것이다. 우리는 오직 이 단계에서 contact normal의 방향에 있는 속도에 관심이 있다. contact coordinates에서, 이것은 X축이고, 그래서 우리의 값은 최종 벡터의 x component이다:
우리는 우리가 이전에 보았던 transpose basis matrix를 사용하여 이 벡터를 contact coordinates로 변환할 필요가 있다. 이것은 우리에게 단위 충격이 발생시킬 속도의 한 벡터를 줄 것이다. 우리는 오직 이 단계에서 contact normal의 방향에 있는 속도에 관심이 있다. contact coordinates에서, 이것은 X축이고, 그래서 우리의 값은 최종 벡터의 x component이다:
glm::vec3 velocityPerUnitImpulseContact = glm::transpose(contactToWorld) * velocityPerUnitImpulse; real angularComponent = velocityPerUnitImpulseContact.x;
여기에서 transformTranspose method는 한 벡터를 한 행렬의 transpose로 transforming하는 효과를 합친 편리한 메소드이다.
비록 우리가 그것을 이 방식으로 구현할 수 있을지라도, 그것을 하는 더 빠른 방법이 있다. 만약 우리가 행렬 곱을 가졌다면,

그러면 그 결과의 x component는 xa+yb+zc이다. 이것은 내적과 같다

여기에서 벡터

는 우리가 basis matrix를 만들 때 보았듯이 contact normal이다. 그래서 우리는 그 전체 matrix transformation을 이 형태의 코드로 바꿀 수 있다.
그러면 그 결과의 x component는 xa+yb+zc이다. 이것은 내적과 같다
여기에서 벡터
는 우리가 basis matrix를 만들 때 보았듯이 contact normal이다. 그래서 우리는 그 전체 matrix transformation을 이 형태의 코드로 바꿀 수 있다.
real angularComponent = glm::dot(velocityPerUnitImpulse, contactNormal);
이 마지막 단계를 생각하는 또 다른 방법이 있다. 그 velocityPerUnitImpulse는 월드 좌표계에서 주어진다. 스칼라곱을 수행하는 것은 contact normal의 방향에서 이 값의 컴포넌트를 찾는 것과 같다. 그리고 거기에서, 벡터로서 contact normal은 월드 좌표에서 주어진다.
내 의견으로는, 좌표변환의 관점에서 생각하는 것이 더 좋다. 왜냐하면 우리가 다음 챕터에서 마찰을 도입할 때, 그 간단한 스칼라곱 트릭은 더 이상 사용될 수 없기 때문이다. 우리가 non-friction case에서 같은 프로세스를 겪고 있다는 것을 깨닫는 것이 중요하다: world to contact coordinates로의 변환으로 끝내는 것.
Putting It Together
그래서 충돌에서 각 오브젝트에 대해, 우리는 이제 unit impulse당 contact point의 속도에서의 변화를 찾을 수 있다.
관련된 두 개의 오브젝트가 있는 contacts에 대해, 우리는 4개의 값들을 가진다: 각 오브젝트에 대해 linear motion과 angular motion에 의해 발생되는 속도. 오직 하나의 rigid body만이 관련된 contacts에 대해 (쯕, 땅과 같은 움직일 수 없는 고정체가 있는 contacts), 우리는 그냥 두 개의 값을 갖는다.
두 경우에, 우리는 최종 값을 단위 충격량 당 속도의 전반적인 변화를 얻기위해 더한다. 그 전체 프로세스는 이 방식으로 구현될 수 있다:
이 코드에서, 첫 번쨰 body가 고려된다. 그것의 속도 변화에서의 회전 component가 계산되고, deltaVelocity component에 위치한다. 그리고 그것의 linear component가 나온다. 만약 두 번째 body가 contact에 있다면, 그러면 그 같은 과정이 반복되고, 그 deltaVelocity는 body 2에 대해 두 개의 컴포넌트들로 증가된다. 그 프로세스의 끝에서, deltaVelocity는 단위 충격량 당 total velocity change를 갖는다.
14.2.3 Impulse Change By Velocity
frictionless collisions에 대해, 이 단계는 엄청 간단하다. 만약 우리가 단위 충격량 당 속도 변화에 대한 단일의 값을 가진다면 (그것을 d로 부른다), 그러면 주어진 속도 변화를 성취하는데 필요한 충격량은

여기에서 v는 속도에서 desired change이고, g는 요구되는 impulse이다.
14.2.4 Calculating The Desired Velocity Change
알고리즘의 이 단계는 두 부분을 가진다. 처음에, 우리는 contact point에서, 현재 closing velocity를 계산할 필요가 있다. 둘 째로, 우리는 우리가 찾는 속도에서의 정확한 변화를 계산할 필요가 있다.
Calculating the Closing Velocity
우리가 필요한 velocity change를 계산할 수 있기 전에, 우리는 contact에서 현재 속도가 무엇인지 알아야만 한다.
우리가 이전에 보았듯이, 속도는 linear and angular component 둘 다 가지고 있다. contact point에서 한 오브젝트의 총 velocity를 계산하기 위해서, 우리는 둘 다 필요하다. 우리는 그것의 linear velocity를 계산하고, rotation만에 의한 contact point의 linear velocity를 계산한다.
우리는 한 오브젝트로부터 직접 linear velocity를 가져올 수 있다; 그것은 rigid body에 저장된다. 회전에 의한 속도를 가져오기 위해, 우리는 방정식 9.5를 다시 사용할 필요가 있다. 한 오브젝트에 대한 contact point의 총 velocity는 다음으로 주어진다
만약 충돌에 연관된 두 개의 bodies가 있다면, 그러면 그 두 번째 body의 값들은 그 velocity vector에 더해질 수 있다.
이것은 우리에게 world coordinates에서의 총 closing velocity를 준다. 우리는 contact coordinates에서의 값을 필요하다. 왜냐하면 우리가 이 속도의 얼마만큼 contact normal의 방향에 있는지와, 이것에 얼마나 접하는지를 이해할 필요가 있기 때문이다. contact normal의 방향에 있지 않은 velocity의 components는 그 오브젝트가 서로를 지나쳐서 얼마나 빠르게 미끄러지는지를 나타낸다: 그것들은 우리가 마찰을 고려할 때 중요하게 될 것이다.
그 변환은 이제 익숙한 방식으로 basis matrix를 사용한다:
frictionless collisions에 대해, 우리는 contact normal의 방향에 놓여있는 벡터의 컴포넌트만을 사용할 것이다. 왜냐하면 벡터가 contact coordinates에 놓여있기 때문에, 이 값은 간다히 그 벡터의 x component이다.
Calculating the Desired Velocity Change
내가 챕터의 시작에서 언급했듯이, 우리가 찾는 속도변화는 우리가 파티클에 대해 사용했던 것과 같은 방정식에 의해 주어진다:

다시 말해서, 우리는 contact에서 모든 존재하는 closing velocity를 제거하길 원하고, 최종 속도가 그것의 원래 값에 c배가 되지만, 반대방향으로 되기위해서 계쏙하기를 원한다. 코드에서 이것은 간단히
만약 그 복구계수 c가 0이라면, 그러면 속도 변화는 모든 존재하는 closing velocity를 제거하는데 충분하다. 다시말해서, 그 오브젝트들은 분리되지 않을 것이다. 만약 그 계수가 1에 가깝다면, 그 오브젝트들은 거의 그것들이 가까워지는 속도와 같은 속도로 분리될 것이다.
그 계수의 값은 충돌에 관련된 물질에 의존한다. 약 0.4의 값은 매우 bouncy하게 보인다, 딱딱한 바닥에서의 고무공처럼. 이 위의 값은 비현실적으로 보이기 시작할 수 있다.
14.2.5 Calculating the Impulse
desired velocity change를 얻고,그 impulse는 방정식 14.3에 의해 주어진다.
우리는 마찰을 걱정하지 않기 때문에, 우리는 오직 contact normal의 방향에서 impulse만을 신경쓴다. contact coordinates에서, contact normal은 X축이고, 그래서 그 최종 impulse vector는

여기에서 g는 이전처럼, impulse이다. 이것은 다음으로 구현된다
이 단계에서, contact coordinates에서 world coordinates로 변환하는 것이 편리하다. 이것은 마지막 단계에서 충격을 적용하는 것을 더 간단하게 만든다. 우리는 좌표를 바꾸기위해 우리의 기저 행렬을 사용하여 이것을 할 수 있다:

이것은 다음으로 구현된다
world coordinates에서 계산된 impulse로, 우리는 진행할 수 있고, 충돌에 있는 오브젝트들에게 그것을 적용할 수 있다.
14.2.6 Applying the Impulse
충격을 적용하기 위해서, 우리는 방정식 7.5와 14.1을 사용한다. 그 첫 번째 것은 우리에게 linear impulsees가 다음의 공식을 따라서 오브젝트의 linear velocity를 바꾼다는 것을 말해준다

그래서 충돌에서 첫 번째 오브젝트에 대한 속도변화는 다음이 될 것이다
그 회전 변화는 방정식 14.1에 의해 주어진다

우리는 또 다시 방정식 14.2를 사용해서 처음에 impulsive torque u를 계산할 필요가 있다:

코드에서 이것은 이것처럼 보인다
이러한 계산은 충돌에서 첫 번째 오브젝트에 대해서 작동하지만, 두 번째에 대해서는 아니다. 만약 두 번째가 있다면. 두 번째 오브젝트에 충격을 적용하기 위해서, 우리는 처음에 관찰을 할 필요가 있다. 우리는 impulse에 대한 단일 값을 계산했지만, 충돌에 관련된 두 개의 오브젝트들이 있을지도 모른다.
chapter7에서 처럼, 추옫ㄹ에 관련된 두 오브젝트들은 같은 크기의 충격을 받을 것이지만, 반대방향이다. 우리가 chapter 2에서 보았듯이, 한 벡터의 방향을 그것의 반대로 바꾸는 것은 그것의 모든 컴포넌트들의 부호를 바꾸는 것과 같다.
우리는 이제까지 contact normal을 사용해서 작업했따. 그것이 collision detector에 의해 생성되기 때문이다. 전통적으로, collision detector은 첫 번째 body의 관점으로부터 contact normal를 생성한다. 그래서 그 계산된 impulse는 첫 번째 body에 대해 옳을 것이다. 그 두번째 body는 반대 방향의 impulse를 받아야만 한다.
우리는 두 번째 body에 대해 우리가 사용한 같은 코드를 사용할 수 있지만, 처음에 우리는 impulse의 부호를 바꿀 필요가 있다
마지막으로, 각 오브젝트에 대해 계산된 그 velocity와 rotation 변화는 직접적으로 rigid body의 속도와 angualr velocity에 적용될 수 있다. 예를들어:
14.3 Resolving Interpenetration
우리는 충돌이 발생했을 때, 속도에서 변화를 나타내는 절차를 다루었다. 만약 우리의 시뮬레이션에서 그 오브젝트들이 정말로 solid하다면, 이것은 필요한 모든 것이다.
불행하게도, 그 오브젝트들은 우리가 충돌이 발생했다는 것을 탐지하기 전에 서로를 지나갈 수 있다. 그 시뮬레이션은 time steps으로 진행하고, 그 동안에, 어떠한 checking도 발생하지 않는다. 충돌 탐지가 발생하는 time step의 끝에서, 두 오브젝트들은 서로에 닿을 수도 있고 지나갔을 수도 있다. 우리는 어떤 방식으로 이 관통을 resolve할 필요가 있다; 만약 그렇지 않는다면, 그 게임에 있는 오브젝트들은 solid하지 않을 것이다.
이것은 우리가 mass-aggregate engine에서 보았던 요구사항과 같은 집합이다. 두 오브젝트들이 관통하는 그 경우에, 그것들을 분리하는 것은 꽤 쉬웠었다. 우리는 각 오브젝트를 contact normal의 line을 따라서, 그것들이 더 이상 교차하지 않는 첫 번째 지점으로 움직였었다.
14.3.1 Choosing A Resolution Method
회전하는 rigid bodies에 대해, 그 상황은 좀 더 복잡하다. 관통을 해결하기 위해 우리가 이용할 수 있는 몇 가지 전략들이 있다.
Linear Projection
우리는 이전처럼 같은 알고리즘을 사용할 수 있다: 각 오브젝트의 위치를 바꾸는 것인데, 그것은 contact normal의 방향으로 떨어지게 하는 것이다. 움직임의 양은 그 오브젝트가 더 이상 닿지 않도록 하는 가능한 가장 작게 되어야 한다.
두 오브젝트들을 포함하는 충돌에 대해서, 각각이 움직이는 양은 그것의 inverse mass에 비례한다. 그러므로, 가벼운 오브젝트는 무거운 오브젝트보다 더 많은 움직임을 가져야만 한다.
이 접근법은 효과가 있고, 구현하기에 간단하다 (사실, 그것은 mass-aggregate engine과 같은 코드를 사용한다). 불행하게도, 그것은 현실적이지 않다. 그림 14.4는 다른 충돌에 의해 땅에 두드려지는 한 block을 보여준다. 만약 우리가 linear projection interpenetration method를 사용한다면, 충돌 후의 상황은 보여지듯이 될 것이다. 이것은 실제 박스가 어떻게 행동하는지를 보여주는 그림의 세 번째 부분과는 대조적이다.
linear projection을 사용하는 것은 오브젝트가 이상하게 씰룩거리게 나타나게 만든다. 만약 너가 오직 spherical objects만을 다루어야 한다면, 그것은 유용하고 매우 빠르다. 어떤 다른 오브젝트에 대해서는, 우리는 좀 더 정교한 것이 필요하다.
Velocity-Based Resolution
몇 가지 물리엔진에서 사용되는 또 다른 전략은 충돌에서의 오브젝트들의 linear and angular velocities를 고려하는 것이다.
그것들의 움직임의 어떤 지점에서, 그 두 오브젝트들은 막 닿았을 것이다. 그 time 후에, 그것들은 time step의 끝에서 관통하기 시작할 것이다. 그 관통을 해결하기 위해, 우리는 그것들을 첫 번째 충돌점으로 되돌아가게 할 수 있다.
실제로, 이 첫 번째 충돌점을 계산하는 것은 어렵고, 걱정할 만큼 가치가 있지 않다. 우리는 충돌 탐지기에 의해 생성된 각 오브젝트에서의 contact point만을 고려하여 그것을 근사시킬 수 있다. 우리는 그것들이 더 이상 contact normal의 방향으로 겹치지 않을 때 까지, 그 경로를 따라서 이러한 두 지점들을 뒤로 움직일 수 있다.
오브젝트들을 뒤로 움직이기 위해, 우리는 어떤 collision resolution이 시작하기 전에, 각 오브젝트의 속도와 회전을 추적할 필요가 있따. 우리는 그러고나서 각 contact point가 취하는 path에대해 한 방정식을 처리하기 위해 이 값들을 사용할 수 있고, 그리고 그것들이 겹치기 시작할 때 처리한다.
이것은 민감한 전략이고, 좋은 결과를 주지만, 시뮬레이션에 부가적인 효과를 도입하는 효과를 갖는다. 그림 14.5는 이 것의 예를 보여준다. 그 오브젝트는 빠른 속도로 지나가면서 땅을 통과한다. 그 velocity-based resolution method는 보여지는 대로 그것의 경로를 따라서 뒤로 움직이게 할 것이다. 사용자에게, 그것은 그 오브젝트가 땅에 부딪히고, 붙어 있는 것처럼 나타날 것이다. 비록 어떠한 마찰이 충돌에 대해 설정되지 않았을지라도.
Nonlinear Projection
세 번째 옵션이자, 내가 이 챕터에서 사용할 것인 이것은 linear projection method에 기반을 둔다. 그 오브젝트를 linearly하게 뒤로 움직이기 보다는, 우리는 관통을 해결하기위해 linear and angular movement의 조합을 사용한다.
그 이론은 같다: 우리는 그것들이 서로 관통하지 않을 때까지 contact normal의 방향으로 두 오브젝트들을 움직인다. 오직 linear이라기 보다는, 그 움직임은 또한 angular component를 가질 수 있다.
충돌에서 각 오브젝트에 대해, 우리는 linear motion과 angular motion의 양을 계산할 필요가 있다. 그래서 그 총 effect는 정확히 그 관통을 해결하기에 충분하다. linear projection에 대해서, 각 오브젝트의 움직임의 양은 각 오브젝트의 inverse mass에 의존할 것이다. linear projection와 다르게, linear and angular velocity 사이의 균형은 각 오브젝트의 inverse inertia tensor에 의존할 것이다.
contact point에서 높은 moment of inertia tensor를 가진 한 오브젝트는 덜 회전할 것이다. 그래서 linear motion으로서 그것의 움직임을 더 취할 거싱다. 그러나 만약 그 오브젝트가 쉽게 회전한다면, 그러면 그 angular motion은 더 많은 부담을 취할 것이다.
그림 14.6은 우리가 그림 14.4와 14.5에서 보았던 같은 상황에 적용된 nonlinear projection을 보여준다. 그 결과는 현실에 있는 것과는 정확히 같지 않지만, 그 결과는 더 믿을만하고, 보통 이상하게 보이지 않는다. 그림 14.7은 shallow impact situation을 보여준다: 그 nonlinear projection method가 어떠한 추가적인 friction을 도입하지 않는 것이다. 사실, 그것은 다소 그 오브젝트가 그렇지 않으면 더 미끄러지게 해서 마찰을 줄인다. 나는 왜 그러는지 모르지만, 실제로, 이것은 추가 friction보다 덜 눈치챌만 하다.
나는 이 섹션에서 나중에 이 알고리즘을 구현하는 것의 세부사항으로 돌아올 것이다.
Relaxation
Relaxation은 엄밀히 말해서, 새로운 resolution method는 아니다. Relaxation은 한 번에 interpenetration의 한 비율만을 해결하고, 어떤 다른 방법과 결합하여 사용될 수 있다. 그러나 그것은 가장 흔하게 nonlinear projection과 사용된다.
Relaxation은 한 오브젝트에 많은 contacts가 있을 때 유용하다. 각 contact가 고려되고 해결될 때, 그것은 오브젝트를 다른 오브젝트가 지금 관통되도록 하는 방식으로 움직이게 할지도 모른다. 한 벽에 있는 벽돌에 대해, 어떤 방향의 움직임은 그것이 다른 벽돌과 관통하도록 야기시킬 것이다. 이것은 interpenetration resolution이 수행되는 순서와 관련된 문제를 일으킬 수 있고, 여전히 눈치챌만한 관통을 가지는 contacts를 가진 그 시뮬레이션을 남길 수 있다.
좀 더 많은 interpenetration resolution steps을 수행하여, 그러나 각각의 것을 오직 interpenetraion의 한 비율로 해결하도록 하여, contacts들의 한 집합은 한 오브젝트가 어디에서 끝나는지에 대해 좀 더 공평함을 가질 수 있다. 각각은 조금씩 resolve되고, 그러고나서 다른 것들은 그것들의 차례를 가진다. 이것은 일반적으로 몇 번 반복된다. 이전에 명백한 관통을 가진 하나 또는 두 개의 contacts가 있는 상황에서, 이 방법은 모든 대립하는 contacts들 사이의 관통을 공유한다, 그리고 이것은 덜 눈치챌만 할지도 모른다.
불행하게도, relaxation은 또한 관통이 충돌이 얼마 없을 때 update의 끝에서 관통이 눈치챌만하게끔 만든다. 대안이 하나의 나쁜 contact를 가지지만, 대부분의 경우에 그것이 바람직하지 않고, full-strength resolution step이 좀 더 시각적으로 기쁠 때, 모든 contacts들이 관통의 작은 정도를 공유하도록 하는 것이 이익이된다.
상대적으로 relaxation을 너의 엔진에 추가하는 것은 간단하다 (너는 그냥 normal resolution algorithm을 수행하기 전에 고정된 비율로 resolve할 penetration을 곱한다). 나는 너에게 relaxation이 없는 기본 시스템을 구성할 것을 충고하고, 그러고나서 만약 너가 필요하다면 그것을 추가해라.
14.3.2 Implementing Nonlinear Projection
nonlinear projection method를 좀 더 세부적으로 보자, 그리고 우리의 코드에서 작동시키자. 우리는 contact의 penetration depth을 알기 시작한다: 이것은 우리가 interpenetration을 해결하는데 필요할 움직임의 총량이다. 우리의 목적은 각 오브젝트에 대해 linear and angular motion에 의해 기여될 이 움직임의 비율을 아는 것이다.
우리는 처음에 한 가정을 한다: 우리는 우리가 재현하고 있는 오브젝트들이 그것들이 변형되도록 서로 눌려진다고 상상한다. 관통의 양이라기 보다, 우리는 contact의 관통 깊이를 마치 그것이 두 bodies에서의 변형의 양인 것처럼 다룬다. 우리가 resolving velocities에 대한 섹션에서 보았듯이, 이 deformation은 그 오브젝트를 분리되게 밀어내는 한 force를 발생시킨다. D'Alembert의 원리에 따라, 이 force는 linear and angular effects 둘 다를 갖는다. 각각의 양은 각 오브젝트의 inverse mass와 inverse inertia tensor에 의존한다.
이 방식으로 관통을 다루는 것은 우리가 section 14.2에서 보았던 것과 같은 수학을 사용하는 것을 허용한다. 우리는 효과적으로 두 오브젝트들이 어떻게 deformation forces로 분리되어 눌려지는지를 모델링한다: 우리는 우리가 필요한 linear and angular components를 찾는 physically realistic method를 사용하는 중이다.
Calculating the Components
속도에 대해, 우리는 단일의 단위 impulse로부터의 rotation change에 의한 속도 변화의 양에만 관심이 있었다. 한 impulse or force가 contact point에 적용될 때, 이 양은 그 오브젝트가 회전되어지는 것에 얼마나 저항하는 지에 대한 측정치이다. 관통을 해결하기 위해, 우리는 정확히 같은 sequence의 방정식을 사용한다. 우리는 linear and angular way 둘 다로, 움직여져야 할 오브젝트의 resistance를 찾는다.
움직여져야 할 한 오브젝트의 resistance가 "inertia"라고 불리는 것을 기억해라. 그래서, 우리는 contact normal의 방향에서 각 오브젝트의 inertia를 찾는 것에 관심이 있다. 이 inertia는 linear and rotational component를 갖는다.
이전 처럼, inertia의 linear component는 간단히 inverse mass이다. 그 angular component는 우리가 이전에 사용했던 같은 sequence의 연산을 사용하여 계산된다. 그 코드는 이렇게 보인다:
이 루프의 마지막에서, 우리는 4 개의 갑을 가지는데 (single-body collisions에 대해서는 두 개), 그것은 우리에게 각 rigid body의 각 component에 의해 해결되어야 할 penetration의 비율을 말해준다. 각 오브젝트가 움직일 필요가 있는 실제 양은 다음에 의해 발견된다
충돌하는 두 번째 오브젝트에 대한 penetration value값은 음수이다. 이것은 우리가 velcoity resolution에 대해 impulse의 부호를 바꾸었던 것과 같은 이유 때문이다: 그 움직임은 그 첫 번째 오브젝트의 관점으로부터 주어진다.
Applying the Movement
linear motion을 적용하는 것은 간단하다. 그 linear move는 요구되는 움직임의 양을 주고, contact normal은 우리에게 그 움직임이 발생해야 하는 방향을 알려준다:
그 angular motion은 좀 더 어렵다. 우리는 우리가 찾는 linear move의 양을 알고있다; 우리는 그것이 우리에게 줄 방향 쿼터니언에서의 변화를 계산할 필요가 있다.
우리는 이것을 세 단계로 한다. 처음에, 우리는 contact point를 한 단위씩 움직이게하는데 필요한 회전을 계산한다. 둘 째로, 우리는 이것에 필요한 단위의 수를 곱한다 (즉, angularMove의 값). 마지막으로, 우리는 방향 쿼터니언에 회전을 적용한다.
우리는 오브젝트가 회전할 필요가 있는 방향을 계산할 수 있는데, 그 회전이 어떤 impulse의 종류에 의해서 발생한다는 가정을 사용한다 (비록 속도가 바뀌지 아닐지라도, 오직 position과 방향만 바뀐다). 만약 한 impulse가 contact point에 발휘된다면, 회전의 변화는 다음과 같을 것이다

이전처럼, 여기에서 q_rel은 contact point의 relative position이고, u는impulse에 생성된 impulsive torque이고, g는 contact normal의 방향에 있는 impulse이다. 코드에서, 이것은
이것은 우리에게 motion의 한 단위를 얻는데 필요한 impulsive torque를 말해준다. 그러나, 우리는 impulses에 관심이 없다 (왜냐하면 우리는 움직일 필요가 있는 총 거리를 알고, 우리는 직접적으로 그 오브젝트를 바꿀 수 있고, 우리는 forces가 motion에 얼마나 바뀌어야 하는지에 대해 걱정할 필요가 없다.)
movement의 한 단위를 얻는데 필요한 회전을 찾기 위해서, 우리는 간단히 inertia로 곱한다:
그 rotationPerMove 벡터는 이제 우리에게 movement의 한 단위를 얻는데 필요한 회전을 말해준다. 그리고 우리는 우리가 원하는 total movement가 angularMove인 것을 안다. 그래서 우리는 적용할 총 회전이 다음이라는 것을 안다.
이 회전을 적용하기 위해서, 우리는 방정식 9.8을 사용한다. 우리가 이전에 정의했던 quaternion 함수 updateByVector를 통해서.
내 의견으로는, 좌표변환의 관점에서 생각하는 것이 더 좋다. 왜냐하면 우리가 다음 챕터에서 마찰을 도입할 때, 그 간단한 스칼라곱 트릭은 더 이상 사용될 수 없기 때문이다. 우리가 non-friction case에서 같은 프로세스를 겪고 있다는 것을 깨닫는 것이 중요하다: world to contact coordinates로의 변환으로 끝내는 것.
Putting It Together
그래서 충돌에서 각 오브젝트에 대해, 우리는 이제 unit impulse당 contact point의 속도에서의 변화를 찾을 수 있다.
관련된 두 개의 오브젝트가 있는 contacts에 대해, 우리는 4개의 값들을 가진다: 각 오브젝트에 대해 linear motion과 angular motion에 의해 발생되는 속도. 오직 하나의 rigid body만이 관련된 contacts에 대해 (쯕, 땅과 같은 움직일 수 없는 고정체가 있는 contacts), 우리는 그냥 두 개의 값을 갖는다.
두 경우에, 우리는 최종 값을 단위 충격량 당 속도의 전반적인 변화를 얻기위해 더한다. 그 전체 프로세스는 이 방식으로 구현될 수 있다:
이 코드에서, 첫 번쨰 body가 고려된다. 그것의 속도 변화에서의 회전 component가 계산되고, deltaVelocity component에 위치한다. 그리고 그것의 linear component가 나온다. 만약 두 번째 body가 contact에 있다면, 그러면 그 같은 과정이 반복되고, 그 deltaVelocity는 body 2에 대해 두 개의 컴포넌트들로 증가된다. 그 프로세스의 끝에서, deltaVelocity는 단위 충격량 당 total velocity change를 갖는다.
14.2.3 Impulse Change By Velocity
frictionless collisions에 대해, 이 단계는 엄청 간단하다. 만약 우리가 단위 충격량 당 속도 변화에 대한 단일의 값을 가진다면 (그것을 d로 부른다), 그러면 주어진 속도 변화를 성취하는데 필요한 충격량은
여기에서 v는 속도에서 desired change이고, g는 요구되는 impulse이다.
14.2.4 Calculating The Desired Velocity Change
알고리즘의 이 단계는 두 부분을 가진다. 처음에, 우리는 contact point에서, 현재 closing velocity를 계산할 필요가 있다. 둘 째로, 우리는 우리가 찾는 속도에서의 정확한 변화를 계산할 필요가 있다.
Calculating the Closing Velocity
우리가 필요한 velocity change를 계산할 수 있기 전에, 우리는 contact에서 현재 속도가 무엇인지 알아야만 한다.
우리가 이전에 보았듯이, 속도는 linear and angular component 둘 다 가지고 있다. contact point에서 한 오브젝트의 총 velocity를 계산하기 위해서, 우리는 둘 다 필요하다. 우리는 그것의 linear velocity를 계산하고, rotation만에 의한 contact point의 linear velocity를 계산한다.
우리는 한 오브젝트로부터 직접 linear velocity를 가져올 수 있다; 그것은 rigid body에 저장된다. 회전에 의한 속도를 가져오기 위해, 우리는 방정식 9.5를 다시 사용할 필요가 있다. 한 오브젝트에 대한 contact point의 총 velocity는 다음으로 주어진다
glm::vec3 velocity = glm::cross(body->getRotation(), relativeContactPosition); velocity += body->getVelocity();
만약 충돌에 연관된 두 개의 bodies가 있다면, 그러면 그 두 번째 body의 값들은 그 velocity vector에 더해질 수 있다.
이것은 우리에게 world coordinates에서의 총 closing velocity를 준다. 우리는 contact coordinates에서의 값을 필요하다. 왜냐하면 우리가 이 속도의 얼마만큼 contact normal의 방향에 있는지와, 이것에 얼마나 접하는지를 이해할 필요가 있기 때문이다. contact normal의 방향에 있지 않은 velocity의 components는 그 오브젝트가 서로를 지나쳐서 얼마나 빠르게 미끄러지는지를 나타낸다: 그것들은 우리가 마찰을 고려할 때 중요하게 될 것이다.
그 변환은 이제 익숙한 방식으로 basis matrix를 사용한다:
contactVelocity = glm::transpose(contactToWorld) * velocity;
frictionless collisions에 대해, 우리는 contact normal의 방향에 놓여있는 벡터의 컴포넌트만을 사용할 것이다. 왜냐하면 벡터가 contact coordinates에 놓여있기 때문에, 이 값은 간다히 그 벡터의 x component이다.
Calculating the Desired Velocity Change
내가 챕터의 시작에서 언급했듯이, 우리가 찾는 속도변화는 우리가 파티클에 대해 사용했던 것과 같은 방정식에 의해 주어진다:
다시 말해서, 우리는 contact에서 모든 존재하는 closing velocity를 제거하길 원하고, 최종 속도가 그것의 원래 값에 c배가 되지만, 반대방향으로 되기위해서 계쏙하기를 원한다. 코드에서 이것은 간단히
real deltaVelocity = -contactVelocity.x * (1 + restitution);
만약 그 복구계수 c가 0이라면, 그러면 속도 변화는 모든 존재하는 closing velocity를 제거하는데 충분하다. 다시말해서, 그 오브젝트들은 분리되지 않을 것이다. 만약 그 계수가 1에 가깝다면, 그 오브젝트들은 거의 그것들이 가까워지는 속도와 같은 속도로 분리될 것이다.
그 계수의 값은 충돌에 관련된 물질에 의존한다. 약 0.4의 값은 매우 bouncy하게 보인다, 딱딱한 바닥에서의 고무공처럼. 이 위의 값은 비현실적으로 보이기 시작할 수 있다.
14.2.5 Calculating the Impulse
desired velocity change를 얻고,그 impulse는 방정식 14.3에 의해 주어진다.
우리는 마찰을 걱정하지 않기 때문에, 우리는 오직 contact normal의 방향에서 impulse만을 신경쓴다. contact coordinates에서, contact normal은 X축이고, 그래서 그 최종 impulse vector는
여기에서 g는 이전처럼, impulse이다. 이것은 다음으로 구현된다
// Calculate the required size of the impulse. impulseContact.x = desiredDeltaVelocity / deltaVelocity; impulseContact.y = 0; impulseContact.z = 0;
이 단계에서, contact coordinates에서 world coordinates로 변환하는 것이 편리하다. 이것은 마지막 단계에서 충격을 적용하는 것을 더 간단하게 만든다. 우리는 좌표를 바꾸기위해 우리의 기저 행렬을 사용하여 이것을 할 수 있다:
이것은 다음으로 구현된다
// Convert impulse to world coordinates glm::vec3 impulse = contactToWorld * impulseContact;
world coordinates에서 계산된 impulse로, 우리는 진행할 수 있고, 충돌에 있는 오브젝트들에게 그것을 적용할 수 있다.
14.2.6 Applying the Impulse
충격을 적용하기 위해서, 우리는 방정식 7.5와 14.1을 사용한다. 그 첫 번째 것은 우리에게 linear impulsees가 다음의 공식을 따라서 오브젝트의 linear velocity를 바꾼다는 것을 말해준다
그래서 충돌에서 첫 번째 오브젝트에 대한 속도변화는 다음이 될 것이다
glm::vec3 velocityChange = impulse * body[0]->getInverseMass();
그 회전 변화는 방정식 14.1에 의해 주어진다
우리는 또 다시 방정식 14.2를 사용해서 처음에 impulsive torque u를 계산할 필요가 있다:
코드에서 이것은 이것처럼 보인다
glm::vec3 impulsiveTorque = glm::cross(impulse, relativeContactPosition); glm::vec3 rotationChange = inverseInertiaTensor * impulsiveTorque;
이러한 계산은 충돌에서 첫 번째 오브젝트에 대해서 작동하지만, 두 번째에 대해서는 아니다. 만약 두 번째가 있다면. 두 번째 오브젝트에 충격을 적용하기 위해서, 우리는 처음에 관찰을 할 필요가 있다. 우리는 impulse에 대한 단일 값을 계산했지만, 충돌에 관련된 두 개의 오브젝트들이 있을지도 모른다.
chapter7에서 처럼, 추옫ㄹ에 관련된 두 오브젝트들은 같은 크기의 충격을 받을 것이지만, 반대방향이다. 우리가 chapter 2에서 보았듯이, 한 벡터의 방향을 그것의 반대로 바꾸는 것은 그것의 모든 컴포넌트들의 부호를 바꾸는 것과 같다.
우리는 이제까지 contact normal을 사용해서 작업했따. 그것이 collision detector에 의해 생성되기 때문이다. 전통적으로, collision detector은 첫 번째 body의 관점으로부터 contact normal를 생성한다. 그래서 그 계산된 impulse는 첫 번째 body에 대해 옳을 것이다. 그 두번째 body는 반대 방향의 impulse를 받아야만 한다.
우리는 두 번째 body에 대해 우리가 사용한 같은 코드를 사용할 수 있지만, 처음에 우리는 impulse의 부호를 바꿀 필요가 있다
// Calculate velocity and rotation change for object one // ... impulse *= -1; // Calculate velocity and rotation change for object two. // ...
마지막으로, 각 오브젝트에 대해 계산된 그 velocity와 rotation 변화는 직접적으로 rigid body의 속도와 angualr velocity에 적용될 수 있다. 예를들어:
body->velocity += velocityChange; body->rotation += rotationChange;
14.3 Resolving Interpenetration
우리는 충돌이 발생했을 때, 속도에서 변화를 나타내는 절차를 다루었다. 만약 우리의 시뮬레이션에서 그 오브젝트들이 정말로 solid하다면, 이것은 필요한 모든 것이다.
불행하게도, 그 오브젝트들은 우리가 충돌이 발생했다는 것을 탐지하기 전에 서로를 지나갈 수 있다. 그 시뮬레이션은 time steps으로 진행하고, 그 동안에, 어떠한 checking도 발생하지 않는다. 충돌 탐지가 발생하는 time step의 끝에서, 두 오브젝트들은 서로에 닿을 수도 있고 지나갔을 수도 있다. 우리는 어떤 방식으로 이 관통을 resolve할 필요가 있다; 만약 그렇지 않는다면, 그 게임에 있는 오브젝트들은 solid하지 않을 것이다.
이것은 우리가 mass-aggregate engine에서 보았던 요구사항과 같은 집합이다. 두 오브젝트들이 관통하는 그 경우에, 그것들을 분리하는 것은 꽤 쉬웠었다. 우리는 각 오브젝트를 contact normal의 line을 따라서, 그것들이 더 이상 교차하지 않는 첫 번째 지점으로 움직였었다.
14.3.1 Choosing A Resolution Method
회전하는 rigid bodies에 대해, 그 상황은 좀 더 복잡하다. 관통을 해결하기 위해 우리가 이용할 수 있는 몇 가지 전략들이 있다.
Linear Projection
우리는 이전처럼 같은 알고리즘을 사용할 수 있다: 각 오브젝트의 위치를 바꾸는 것인데, 그것은 contact normal의 방향으로 떨어지게 하는 것이다. 움직임의 양은 그 오브젝트가 더 이상 닿지 않도록 하는 가능한 가장 작게 되어야 한다.
두 오브젝트들을 포함하는 충돌에 대해서, 각각이 움직이는 양은 그것의 inverse mass에 비례한다. 그러므로, 가벼운 오브젝트는 무거운 오브젝트보다 더 많은 움직임을 가져야만 한다.
이 접근법은 효과가 있고, 구현하기에 간단하다 (사실, 그것은 mass-aggregate engine과 같은 코드를 사용한다). 불행하게도, 그것은 현실적이지 않다. 그림 14.4는 다른 충돌에 의해 땅에 두드려지는 한 block을 보여준다. 만약 우리가 linear projection interpenetration method를 사용한다면, 충돌 후의 상황은 보여지듯이 될 것이다. 이것은 실제 박스가 어떻게 행동하는지를 보여주는 그림의 세 번째 부분과는 대조적이다.
linear projection을 사용하는 것은 오브젝트가 이상하게 씰룩거리게 나타나게 만든다. 만약 너가 오직 spherical objects만을 다루어야 한다면, 그것은 유용하고 매우 빠르다. 어떤 다른 오브젝트에 대해서는, 우리는 좀 더 정교한 것이 필요하다.
Velocity-Based Resolution
몇 가지 물리엔진에서 사용되는 또 다른 전략은 충돌에서의 오브젝트들의 linear and angular velocities를 고려하는 것이다.
그것들의 움직임의 어떤 지점에서, 그 두 오브젝트들은 막 닿았을 것이다. 그 time 후에, 그것들은 time step의 끝에서 관통하기 시작할 것이다. 그 관통을 해결하기 위해, 우리는 그것들을 첫 번째 충돌점으로 되돌아가게 할 수 있다.
실제로, 이 첫 번째 충돌점을 계산하는 것은 어렵고, 걱정할 만큼 가치가 있지 않다. 우리는 충돌 탐지기에 의해 생성된 각 오브젝트에서의 contact point만을 고려하여 그것을 근사시킬 수 있다. 우리는 그것들이 더 이상 contact normal의 방향으로 겹치지 않을 때 까지, 그 경로를 따라서 이러한 두 지점들을 뒤로 움직일 수 있다.
오브젝트들을 뒤로 움직이기 위해, 우리는 어떤 collision resolution이 시작하기 전에, 각 오브젝트의 속도와 회전을 추적할 필요가 있따. 우리는 그러고나서 각 contact point가 취하는 path에대해 한 방정식을 처리하기 위해 이 값들을 사용할 수 있고, 그리고 그것들이 겹치기 시작할 때 처리한다.
이것은 민감한 전략이고, 좋은 결과를 주지만, 시뮬레이션에 부가적인 효과를 도입하는 효과를 갖는다. 그림 14.5는 이 것의 예를 보여준다. 그 오브젝트는 빠른 속도로 지나가면서 땅을 통과한다. 그 velocity-based resolution method는 보여지는 대로 그것의 경로를 따라서 뒤로 움직이게 할 것이다. 사용자에게, 그것은 그 오브젝트가 땅에 부딪히고, 붙어 있는 것처럼 나타날 것이다. 비록 어떠한 마찰이 충돌에 대해 설정되지 않았을지라도.
Nonlinear Projection
세 번째 옵션이자, 내가 이 챕터에서 사용할 것인 이것은 linear projection method에 기반을 둔다. 그 오브젝트를 linearly하게 뒤로 움직이기 보다는, 우리는 관통을 해결하기위해 linear and angular movement의 조합을 사용한다.
그 이론은 같다: 우리는 그것들이 서로 관통하지 않을 때까지 contact normal의 방향으로 두 오브젝트들을 움직인다. 오직 linear이라기 보다는, 그 움직임은 또한 angular component를 가질 수 있다.
충돌에서 각 오브젝트에 대해, 우리는 linear motion과 angular motion의 양을 계산할 필요가 있다. 그래서 그 총 effect는 정확히 그 관통을 해결하기에 충분하다. linear projection에 대해서, 각 오브젝트의 움직임의 양은 각 오브젝트의 inverse mass에 의존할 것이다. linear projection와 다르게, linear and angular velocity 사이의 균형은 각 오브젝트의 inverse inertia tensor에 의존할 것이다.
contact point에서 높은 moment of inertia tensor를 가진 한 오브젝트는 덜 회전할 것이다. 그래서 linear motion으로서 그것의 움직임을 더 취할 거싱다. 그러나 만약 그 오브젝트가 쉽게 회전한다면, 그러면 그 angular motion은 더 많은 부담을 취할 것이다.
그림 14.6은 우리가 그림 14.4와 14.5에서 보았던 같은 상황에 적용된 nonlinear projection을 보여준다. 그 결과는 현실에 있는 것과는 정확히 같지 않지만, 그 결과는 더 믿을만하고, 보통 이상하게 보이지 않는다. 그림 14.7은 shallow impact situation을 보여준다: 그 nonlinear projection method가 어떠한 추가적인 friction을 도입하지 않는 것이다. 사실, 그것은 다소 그 오브젝트가 그렇지 않으면 더 미끄러지게 해서 마찰을 줄인다. 나는 왜 그러는지 모르지만, 실제로, 이것은 추가 friction보다 덜 눈치챌만 하다.
나는 이 섹션에서 나중에 이 알고리즘을 구현하는 것의 세부사항으로 돌아올 것이다.
Relaxation
Relaxation은 엄밀히 말해서, 새로운 resolution method는 아니다. Relaxation은 한 번에 interpenetration의 한 비율만을 해결하고, 어떤 다른 방법과 결합하여 사용될 수 있다. 그러나 그것은 가장 흔하게 nonlinear projection과 사용된다.
Relaxation은 한 오브젝트에 많은 contacts가 있을 때 유용하다. 각 contact가 고려되고 해결될 때, 그것은 오브젝트를 다른 오브젝트가 지금 관통되도록 하는 방식으로 움직이게 할지도 모른다. 한 벽에 있는 벽돌에 대해, 어떤 방향의 움직임은 그것이 다른 벽돌과 관통하도록 야기시킬 것이다. 이것은 interpenetration resolution이 수행되는 순서와 관련된 문제를 일으킬 수 있고, 여전히 눈치챌만한 관통을 가지는 contacts를 가진 그 시뮬레이션을 남길 수 있다.
좀 더 많은 interpenetration resolution steps을 수행하여, 그러나 각각의 것을 오직 interpenetraion의 한 비율로 해결하도록 하여, contacts들의 한 집합은 한 오브젝트가 어디에서 끝나는지에 대해 좀 더 공평함을 가질 수 있다. 각각은 조금씩 resolve되고, 그러고나서 다른 것들은 그것들의 차례를 가진다. 이것은 일반적으로 몇 번 반복된다. 이전에 명백한 관통을 가진 하나 또는 두 개의 contacts가 있는 상황에서, 이 방법은 모든 대립하는 contacts들 사이의 관통을 공유한다, 그리고 이것은 덜 눈치챌만 할지도 모른다.
불행하게도, relaxation은 또한 관통이 충돌이 얼마 없을 때 update의 끝에서 관통이 눈치챌만하게끔 만든다. 대안이 하나의 나쁜 contact를 가지지만, 대부분의 경우에 그것이 바람직하지 않고, full-strength resolution step이 좀 더 시각적으로 기쁠 때, 모든 contacts들이 관통의 작은 정도를 공유하도록 하는 것이 이익이된다.
상대적으로 relaxation을 너의 엔진에 추가하는 것은 간단하다 (너는 그냥 normal resolution algorithm을 수행하기 전에 고정된 비율로 resolve할 penetration을 곱한다). 나는 너에게 relaxation이 없는 기본 시스템을 구성할 것을 충고하고, 그러고나서 만약 너가 필요하다면 그것을 추가해라.
14.3.2 Implementing Nonlinear Projection
nonlinear projection method를 좀 더 세부적으로 보자, 그리고 우리의 코드에서 작동시키자. 우리는 contact의 penetration depth을 알기 시작한다: 이것은 우리가 interpenetration을 해결하는데 필요할 움직임의 총량이다. 우리의 목적은 각 오브젝트에 대해 linear and angular motion에 의해 기여될 이 움직임의 비율을 아는 것이다.
우리는 처음에 한 가정을 한다: 우리는 우리가 재현하고 있는 오브젝트들이 그것들이 변형되도록 서로 눌려진다고 상상한다. 관통의 양이라기 보다, 우리는 contact의 관통 깊이를 마치 그것이 두 bodies에서의 변형의 양인 것처럼 다룬다. 우리가 resolving velocities에 대한 섹션에서 보았듯이, 이 deformation은 그 오브젝트를 분리되게 밀어내는 한 force를 발생시킨다. D'Alembert의 원리에 따라, 이 force는 linear and angular effects 둘 다를 갖는다. 각각의 양은 각 오브젝트의 inverse mass와 inverse inertia tensor에 의존한다.
이 방식으로 관통을 다루는 것은 우리가 section 14.2에서 보았던 것과 같은 수학을 사용하는 것을 허용한다. 우리는 효과적으로 두 오브젝트들이 어떻게 deformation forces로 분리되어 눌려지는지를 모델링한다: 우리는 우리가 필요한 linear and angular components를 찾는 physically realistic method를 사용하는 중이다.
Calculating the Components
속도에 대해, 우리는 단일의 단위 impulse로부터의 rotation change에 의한 속도 변화의 양에만 관심이 있었다. 한 impulse or force가 contact point에 적용될 때, 이 양은 그 오브젝트가 회전되어지는 것에 얼마나 저항하는 지에 대한 측정치이다. 관통을 해결하기 위해, 우리는 정확히 같은 sequence의 방정식을 사용한다. 우리는 linear and angular way 둘 다로, 움직여져야 할 오브젝트의 resistance를 찾는다.
움직여져야 할 한 오브젝트의 resistance가 "inertia"라고 불리는 것을 기억해라. 그래서, 우리는 contact normal의 방향에서 각 오브젝트의 inertia를 찾는 것에 관심이 있다. 이 inertia는 linear and rotational component를 갖는다.
이전 처럼, inertia의 linear component는 간단히 inverse mass이다. 그 angular component는 우리가 이전에 사용했던 같은 sequence의 연산을 사용하여 계산된다. 그 코드는 이렇게 보인다:
// We need to work out the inertia of each object in the direction // of the contact normal, due to angular inertia only. for(unsigned i = 0; i < 2; ++i) { if(body[i]) { glm::mat3 inverseInertiaTensor = body[i]->getInverseInertiaTensorWorld(); // use the same procedure as for calculating frictionless // velocity change to work out the angular inertia. glm::vec3 angularInertiaWorld = glm::cross(relativeContactPosition[i], contactNormal); angularInertiaWorld = inverseInertiaTensor * angularInertiaWorld; angularInertiaWorld = glm::cross(angularInertiaWorld, relativeContactPosition[i]); angularInertia[i] = glm::dot(angularInertiaWorld, contactNormal); // The linear component is simply the inverse mass linearInertia[i] = body[i]->getInverseMass(); totalInertia += linearInertia[i] + angularInertia[i]; } }
이 루프의 마지막에서, 우리는 4 개의 갑을 가지는데 (single-body collisions에 대해서는 두 개), 그것은 우리에게 각 rigid body의 각 component에 의해 해결되어야 할 penetration의 비율을 말해준다. 각 오브젝트가 움직일 필요가 있는 실제 양은 다음에 의해 발견된다
real inverseInertia = 1 / totalinertia; linearMove[0] = penetration * linearInertia[0] * inverseInertia; linearMove[1] = -penetration * linearInertia[1] * inverseInertia; angularMove[0] = penetration * angularInertia[0] * inverseInertia; angularMove[1] = -penetration * angularInertia[1] * inverseInertia;
충돌하는 두 번째 오브젝트에 대한 penetration value값은 음수이다. 이것은 우리가 velcoity resolution에 대해 impulse의 부호를 바꾸었던 것과 같은 이유 때문이다: 그 움직임은 그 첫 번째 오브젝트의 관점으로부터 주어진다.
Applying the Movement
linear motion을 적용하는 것은 간단하다. 그 linear move는 요구되는 움직임의 양을 주고, contact normal은 우리에게 그 움직임이 발생해야 하는 방향을 알려준다:
body[i]->position += contactNormal * linearMove[i];
그 angular motion은 좀 더 어렵다. 우리는 우리가 찾는 linear move의 양을 알고있다; 우리는 그것이 우리에게 줄 방향 쿼터니언에서의 변화를 계산할 필요가 있다.
우리는 이것을 세 단계로 한다. 처음에, 우리는 contact point를 한 단위씩 움직이게하는데 필요한 회전을 계산한다. 둘 째로, 우리는 이것에 필요한 단위의 수를 곱한다 (즉, angularMove의 값). 마지막으로, 우리는 방향 쿼터니언에 회전을 적용한다.
우리는 오브젝트가 회전할 필요가 있는 방향을 계산할 수 있는데, 그 회전이 어떤 impulse의 종류에 의해서 발생한다는 가정을 사용한다 (비록 속도가 바뀌지 아닐지라도, 오직 position과 방향만 바뀐다). 만약 한 impulse가 contact point에 발휘된다면, 회전의 변화는 다음과 같을 것이다
이전처럼, 여기에서 q_rel은 contact point의 relative position이고, u는impulse에 생성된 impulsive torque이고, g는 contact normal의 방향에 있는 impulse이다. 코드에서, 이것은
glm::vec3 inverseInertiaTensor = body->getInvserInertiaTensorWorld(); glm::vec3 impulsiveTorque = glm::cross(relativeContactPosition, contactNormal); glm::vec3 impulsePerMove = inverseInertiaTensor * impulsiveTorque;
이것은 우리에게 motion의 한 단위를 얻는데 필요한 impulsive torque를 말해준다. 그러나, 우리는 impulses에 관심이 없다 (왜냐하면 우리는 움직일 필요가 있는 총 거리를 알고, 우리는 직접적으로 그 오브젝트를 바꿀 수 있고, 우리는 forces가 motion에 얼마나 바뀌어야 하는지에 대해 걱정할 필요가 없다.)
movement의 한 단위를 얻는데 필요한 회전을 찾기 위해서, 우리는 간단히 inertia로 곱한다:
glm::vec3 rotationPerMove = impulseMove * 1 / angularInertia;
그 rotationPerMove 벡터는 이제 우리에게 movement의 한 단위를 얻는데 필요한 회전을 말해준다. 그리고 우리는 우리가 원하는 total movement가 angularMove인 것을 안다. 그래서 우리는 적용할 총 회전이 다음이라는 것을 안다.
glm::vec3 rotation = rotationPerMove * angularMove;
이 회전을 적용하기 위해서, 우리는 방정식 9.8을 사용한다. 우리가 이전에 정의했던 quaternion 함수 updateByVector를 통해서.
14.3.3 Avoiding Excessive Rotation
지금까지 보여진 알고리즘을 다루는데 두 가지 문제들이 있다. 그 첫 번째는, 주석없이 들어온 가정이고, 두 번째는 불안정성과 이상하게 보이는 행동을 발생시킬 수 있는 잠재적인 문제이다. 둘 다 회전될 오브젝트와 너무 관련있다, 그것들이 penetration에서 나올 때.
그림 14.8은 심하게 관통된 한 오브젝트를 보여준다. 만약 그 오브젝트의 moment of inertia가 작고, 그것의 무게가 크다면, 그러면 대부분의 extraction이 angular movement에 들어갈 것이다. 명백히, angular movement가 아무리 많이 부과될지라도, 그 contact point는 그 오브젝트 밖으로 나가지 않을 것이다. 그 예제는 매우 극한의 것이지만, 그 문제는 매우 real한 것이다.
한 오브젝트가 impulsive torque로부터 회전하기 시작한 순간에, 그 contact point 또한 움직이기 시작할것이다. 우리는 우리가 contact point의 위치에서의 즉각적인 변화(즉, 그것의 속도)를 취할 수있고, 회전이 얼마나 필요한지 처리하기 위해 그것을 사용할 수 있다고가정한다. 이 가정을 하는 것은 회전이 얼마나 적용하는 지에 대한 솔루션이 항상 있을 거라는 것을 의미한다. 심지어, 어떠한 솔루션도 존재하지 않는 경우에도 (그림 14.8같은 경우에).
사실, 우리는 contact point가 같은 비율로 그것의 초기 방향으로 영원히 계속해서 움직일 것이라고 가정했다. 명백히 이것은 잘못된 가정이다: 그 contact point는 그것이 center of mass를 중심으로 회전함에 따라, 그것의 움직임 방향을 바꿀 것이다. 작은 회전에 대해서, 그 가정은 꽤 좋다. 그리고 우리는 대부분의 관통이 너무 크지 않다고 희망한다.
큰 회전에 대해서, 그러나 우리는 또 다른 문제를 가진다. 우리는 우리가 그 오브젝트를 너무 멀리 회전시켜서, 그 contact point가 다시 가까워지기 시작하는 가능성을 가진다. 또는 그 오브젝트의 다른 부분이 관통할 가능성을 가진다. 그림 14.9는 이 경우를 보여준다. 심지어 그 오브젝트의 적당한 회전은 다른 관통이 발생하도록 야기시킬 수 있다.
두 문제에 대해, 우리는 우리의 penetration resolution의 일부가 될 수 있는 회전의 양을 제한할 필요가 있다. 이 값을 작게 유지하는 것은, 우리의 small rotation assumption이 유효하다는 것을 의미하고, 우리가 한 contact를 해결하는 동안 다른 관통이 발생할 가능성을 최소화 한다는 것을 의미한다.
우리가 원하는 linear and angular motion의 양은 4개의 변수로 계산되고 저장되어있다 (single-body collisions에 대해서는 두 개):
지금까지 보여진 알고리즘을 다루는데 두 가지 문제들이 있다. 그 첫 번째는, 주석없이 들어온 가정이고, 두 번째는 불안정성과 이상하게 보이는 행동을 발생시킬 수 있는 잠재적인 문제이다. 둘 다 회전될 오브젝트와 너무 관련있다, 그것들이 penetration에서 나올 때.
그림 14.8은 심하게 관통된 한 오브젝트를 보여준다. 만약 그 오브젝트의 moment of inertia가 작고, 그것의 무게가 크다면, 그러면 대부분의 extraction이 angular movement에 들어갈 것이다. 명백히, angular movement가 아무리 많이 부과될지라도, 그 contact point는 그 오브젝트 밖으로 나가지 않을 것이다. 그 예제는 매우 극한의 것이지만, 그 문제는 매우 real한 것이다.
한 오브젝트가 impulsive torque로부터 회전하기 시작한 순간에, 그 contact point 또한 움직이기 시작할것이다. 우리는 우리가 contact point의 위치에서의 즉각적인 변화(즉, 그것의 속도)를 취할 수있고, 회전이 얼마나 필요한지 처리하기 위해 그것을 사용할 수 있다고가정한다. 이 가정을 하는 것은 회전이 얼마나 적용하는 지에 대한 솔루션이 항상 있을 거라는 것을 의미한다. 심지어, 어떠한 솔루션도 존재하지 않는 경우에도 (그림 14.8같은 경우에).
사실, 우리는 contact point가 같은 비율로 그것의 초기 방향으로 영원히 계속해서 움직일 것이라고 가정했다. 명백히 이것은 잘못된 가정이다: 그 contact point는 그것이 center of mass를 중심으로 회전함에 따라, 그것의 움직임 방향을 바꿀 것이다. 작은 회전에 대해서, 그 가정은 꽤 좋다. 그리고 우리는 대부분의 관통이 너무 크지 않다고 희망한다.
큰 회전에 대해서, 그러나 우리는 또 다른 문제를 가진다. 우리는 우리가 그 오브젝트를 너무 멀리 회전시켜서, 그 contact point가 다시 가까워지기 시작하는 가능성을 가진다. 또는 그 오브젝트의 다른 부분이 관통할 가능성을 가진다. 그림 14.9는 이 경우를 보여준다. 심지어 그 오브젝트의 적당한 회전은 다른 관통이 발생하도록 야기시킬 수 있다.
두 문제에 대해, 우리는 우리의 penetration resolution의 일부가 될 수 있는 회전의 양을 제한할 필요가 있다. 이 값을 작게 유지하는 것은, 우리의 small rotation assumption이 유효하다는 것을 의미하고, 우리가 한 contact를 해결하는 동안 다른 관통이 발생할 가능성을 최소화 한다는 것을 의미한다.
우리가 원하는 linear and angular motion의 양은 4개의 변수로 계산되고 저장되어있다 (single-body collisions에 대해서는 두 개):
linearMove[0] linearMove[1] angularMove[0] angularMove[1]
우리는간단히 angularMove의 값이 너무 크지 않다는 것을 체크할 수 있다. 만약 그것들이 크다면, 우리는 그것들의 몇 가지 burden을 대응되는 linearMove component로 전환할 수 있다.
그러나 무엇이 "too great"하는가? 이것은 우리가 그 물리 엔진을 튜닝하는 것의 black art로 내려가는 부분이다. 나는 어떤 특별한 전략을 선택하는 것에 대해 민감한 논리적 논쟁을 만나지 않았다.
몇몇 개발자들은 고정값을 사용한다: 예를들어, angular move value가 0.5보다 더 크게 되는 것을 허용하지 않는다. 이것은 시뮬레이션에 있는 오브젝트들이 모두 대강 같은 크기인 한 잘 작동한다. 만약 몇 오브젝트들이 매우 크다면, 그러면 그것들에 대한 적절한 limit은 smaller objects에 대해 적합하지 않고, 역으로도 그렇다.
또한 그 오브젝트가 만들 수 있는 회전의 일부의 관점에서 그 limit을 표현하는 것이 가능하다. 우리는 그 회전을 제한할 수 있는데, 예를들어 그 오브젝트가 45도 이상 회전하지 않도록 하기 위해서이다. 이것은 사이즈에서 차이를 고려하지만, 특정한 rotation 축에 대해 동등한 equivalent angular move를 처리하는 것은 좀 더 복잡하다.
간단한 대안은, angular move를 그 오브젝트의 크기로 스케일링하는 것이다( 거기에서 그 오브젝트의 크기는 relative contact position vector의 크기로 근사되어질 수 있다). 그러므로 더 큰 오브젝트들은 더욱 많은 angular movement를 가질 수 있다. 이것은 내가 코드에서 사용한 접근법이다.
그러나 무엇이 "too great"하는가? 이것은 우리가 그 물리 엔진을 튜닝하는 것의 black art로 내려가는 부분이다. 나는 어떤 특별한 전략을 선택하는 것에 대해 민감한 논리적 논쟁을 만나지 않았다.
몇몇 개발자들은 고정값을 사용한다: 예를들어, angular move value가 0.5보다 더 크게 되는 것을 허용하지 않는다. 이것은 시뮬레이션에 있는 오브젝트들이 모두 대강 같은 크기인 한 잘 작동한다. 만약 몇 오브젝트들이 매우 크다면, 그러면 그것들에 대한 적절한 limit은 smaller objects에 대해 적합하지 않고, 역으로도 그렇다.
또한 그 오브젝트가 만들 수 있는 회전의 일부의 관점에서 그 limit을 표현하는 것이 가능하다. 우리는 그 회전을 제한할 수 있는데, 예를들어 그 오브젝트가 45도 이상 회전하지 않도록 하기 위해서이다. 이것은 사이즈에서 차이를 고려하지만, 특정한 rotation 축에 대해 동등한 equivalent angular move를 처리하는 것은 좀 더 복잡하다.
간단한 대안은, angular move를 그 오브젝트의 크기로 스케일링하는 것이다( 거기에서 그 오브젝트의 크기는 relative contact position vector의 크기로 근사되어질 수 있다). 그러므로 더 큰 오브젝트들은 더욱 많은 angular movement를 가질 수 있다. 이것은 내가 코드에서 사용한 접근법이다.
real limit = angularLimitConstant * glm::length(relativeContactPosition); // Check the angular move is within limits if(real_abs(angularMove) > limit) { real totalMove = linearMove + angularMove; // Set the new angular move, with the same sign as before if(angularMove >= 0) angularMove = limit; else angularMove = -limit; // Make the linearmove take the extra slack. linearMove = totalMove - angularMove; }
angularLimit Constant의 값은 너의 특정한 시뮬레이션과 play함으로써 결정될 필요가 있다. 나는 약 0.2의 값이 좋은 결과를 주는 것을 발견했다. 비록 더 낮은 값이 매우 bouncy collisions가 필요한 상황에서 더 나을지라도.
14.4 The Collision Resolution Process
지금까지, 우리는 velocity와 interpenetration에 대한 특정한 충돌을 해결하는 것을 보았었다. 한 충돌을 스스로 해결하는 것은 매우 유용하지 않다.
충돌 탐지기는 어떤 수의 contacts를 생성하고, 모든 이러한 것들은 처리될 필요가 있다. 우리는 어떤 개수의 충돌이든 한 번에 처리되는 framework를 구성할 필요가 있다. 이 챕터의 마지막 섹션은 이전의 알고리즘을 함께 끝에서 엮는다. 우리는 friction이 필요없는 시뮬레이션을 위해 사용될 수 있는 완전한 collision resolution system을 가지게 될 것이다. 다음 두 챕터에서, 우리는 friction을 다루고, 속도를 개선하고, 서로에게 resting하고 있는 오브젝트에 대한 안정성을 높이기 위해, 그 엔진을 확장할 것이다.
나는 책의 인트로에서, 일련의 충돌들을 어떻게 해결하는지에 대한 선택이 물리 시스템이 만들어지는 방법의 중심부라고 언급했다. 대부분의 상업물리 middleware packages는 동시에 모든 충돌들을 처리한다 (또는 적어도, 그것들을 동시에 처리될 그룹으로 batch한다). 이것은 그것들이 한 contact에 만들어진 adjustments가 다른 것들을 disturb하지 않도록 하는 것을 허용한다.
14.4 The Collision Resolution Process
지금까지, 우리는 velocity와 interpenetration에 대한 특정한 충돌을 해결하는 것을 보았었다. 한 충돌을 스스로 해결하는 것은 매우 유용하지 않다.
충돌 탐지기는 어떤 수의 contacts를 생성하고, 모든 이러한 것들은 처리될 필요가 있다. 우리는 어떤 개수의 충돌이든 한 번에 처리되는 framework를 구성할 필요가 있다. 이 챕터의 마지막 섹션은 이전의 알고리즘을 함께 끝에서 엮는다. 우리는 friction이 필요없는 시뮬레이션을 위해 사용될 수 있는 완전한 collision resolution system을 가지게 될 것이다. 다음 두 챕터에서, 우리는 friction을 다루고, 속도를 개선하고, 서로에게 resting하고 있는 오브젝트에 대한 안정성을 높이기 위해, 그 엔진을 확장할 것이다.
나는 책의 인트로에서, 일련의 충돌들을 어떻게 해결하는지에 대한 선택이 물리 시스템이 만들어지는 방법의 중심부라고 언급했다. 대부분의 상업물리 middleware packages는 동시에 모든 충돌들을 처리한다 (또는 적어도, 그것들을 동시에 처리될 그룹으로 batch한다). 이것은 그것들이 한 contact에 만들어진 adjustments가 다른 것들을 disturb하지 않도록 하는 것을 허용한다.
우리는 다소 다른 길로 갈 것이다. 우리의 resolution system은 각 충돌을 차례대로 볼 것이고, 그것을 옳게 할 것이다. 그것은 심각성의 순서로 충돌을 처리할 것이다 (빠른 충돌들은 첫 번째로다뤄진다). 한 충돌을 이 방식으로 해결하는 것은 다른 것들이 더 악화되게 만들 지도 모른다. 우리는 그 코드를 구조화 시켜야만 하는데, 그것이 이 문제를 고려할 수 있게 하기 위해서이다.
chapter 18에서, 나는 simultaneous resolution approaches에 대해 볼 것이다. 그러나, 너의 물리의 필요가 그것들의 정교함을 필요하지 않을 가능성이 있다. 그것들이 이 책의 이 부분에서의 방법들보다 더 안정되고 정확하지만, 그것들은 훨씬 더 복잡하고, 상당히 더 느릴 수 있다.
chapter 18에서, 나는 simultaneous resolution approaches에 대해 볼 것이다. 그러나, 너의 물리의 필요가 그것들의 정교함을 필요하지 않을 가능성이 있다. 그것들이 이 책의 이 부분에서의 방법들보다 더 안정되고 정확하지만, 그것들은 훨씬 더 복잡하고, 상당히 더 느릴 수 있다.
14.4.1 The Collision Resolution Pipeline
그림 14.10은 도식으로 나타낸 collision resolution process 보여준다. 충돌들은 관련된 오브젝트의 충돌 geometry를 기반으로 충돌 탐지기에 의해 생성된다. 이러한 충돌들은 collision resolution routine으로 넘겨진다, 관련된 오브젝트들에 대한 rigid-body data와 함께.
그 collision resolution routine은 두 개의 컴포넌트들을 갖는다: velocity resolution system and a penetration resolution system. 이러한 것은 이 챕터의 대다수를 구성하는 두 개의알고리즘과 대응된다.
이러한 두 단계들은 서로 독립적이다. 그 오브젝트들의 속도를 바꾸는 것은 그것들이얼마나 관통해 있는지에 영향을 미치지 않고, 역으로도 그렇다.
모든 충돌들이 동시에 처리되어 매우 정교한 velocity resolution을 하는 물리엔진들은 종종 우리가 이전에 구현했던 알고리즘을 사용하는 separate penetration resolver를 가진다.
이 챕터에서 우리가 구현하는 그 collision resolver는 한 클래스에 설정된다: CollisionResolver. 그것은 전체 collisions의 집합과 duration of the frame을 취하는 resolveContacts method를 가지고 있고, 그래서 그것은 세 단계로 그 resolution을 수행한다: 처음에 그것은 각 contact에 대한 internal data를 계산한다; 그러고나서 그것은 그 contacts들을 penetration resolver로 넘긴다; 그러고나서 그것들은 velocity resolver로 넘겨진다:
우리는 또한 contact data structure에 friend declaration을 추가한다. 이것은 resolver가 그것의 internals에 대해 직접적인 접근을 할 수 이게 하기 위해서이다.
14.4.2 Preparing Contact Data
우리는 각 contact에 대해 velocity resolution step뿐만 아니라, penetration resolution step을 수행할 것이기 때문에, 두 단계가 하나의 central location에서 필요한 정보를 계산하는 것이 유용하다. 게다가, resolution의 올바른 순서를 처리하는데 요구되는 추가 정보가 계산되어야만 한다.
그 첫 번째 카테고리는 data의 two bits 이다:
그 preparation routine은 오직 각 contact를 차례로 호출할 필요가 있고, 그것에게 적절한 데이터를 계산하라고 요청한다.
contact의 calculateInternals method에서, 우리는 이러한 데이터들 각각을 계산할 필요가 있다: contact basis, relative position, relative velocity:
그 contact basis method는 section 14.2.1에 묘사되어있다. 그 relative position calculate은 오히려 간단하다. 나머지 두 components - swapping bodies and calculating relative velocity - 는 몇 가지 주석이 필요하다.
Swapping Bodies
그 첫 번째 두 라인들은 만약 충돌에 오직 하나의 오브젝트만 있다면, 그것이 그 배열의 zero position에 있도록 하는 것을 보장한다. 지금까지 우리는 이것이 사실이라고 가정했다. 만약 너의 충돌 탐지기가 이 방식으로 단일 오브젝트의 충돌만을 반환하도록 보장된다면, 그러면 너는 이 코드를 무시할 수 있다.
bodies를 swap하기 위해, 우리는 두 개의 body reference를 바꿀 필요가 있고, 또한 그 contact normal의 방향을 반대로 할 필요가 있다. 그 contact normal은 항상 그 첫 번째 오브젝트의 관점에서 주어진다. 만약 그 bodies가 swapped된다면, 그러면 이것이 바뀔 필요가 있다.
Calculating Relative Velocity
우리가 관심이 있는 relative velocity는 contact point에서 두 오브젝트들의 closing velocity이다. 이것은 오브젝트가 bounce한 후에, desired final velocity를 처리하기 위해 사용될 것이다.
그 velocity는 contact coordinates에서 주어질 필요가 있다. 그것의 x값은 contact normal 방향에서의 속도를 줄 것이다. 그리고 그것의 y와 z값은 contact가 발생하는 sliding의 양을 줄 것이다. 우리는 우리가 friction을 만날 때, 이 두 값을 사용할 것이다.
우리가 보았듯이, 한 점에서의 속도는 두 개의 linear and angular components로 구성된다:

여기에서 q_rel은 우리가 관심있는 그 점의 위치이다. 이것은 object의 center of mass를 기준으로 한다; p는 그 오브젝트의 center of mass의 위치이다 (즉, dot(p)는 그 전체 오브젝트의 linear velocity이다); 그리고 dot(theta)는 그 오브젝트의 angular velocity이다.
contact coordinates에서 그 속도를 계산하기 위해, 우리는 이 방정식을 사용하고, 그러고나서 그 결과를 contact basis matrix의 transpose로 변환한다:
그 calculateInternals method는 contact point에서 전체 closing velocity를 찾는다, second body의 closing velocity를 첫 번째로부터 빼서.
이 알고리즘은 contact basis matrix와 relative contact positions을 사용하기 때문에, 그것은 마지막에 되어야만 한다.
14.4.3 Resolving Penetration
우리는 각 contact를 방문했고, 두 개의 resolution step에 필요할 그 데이터를 계산했다. 우리는 이제 우리의 주의를 모든 contacts에 대한 interpenetration을 해결하는데 돌린다. 우리는 각 contact를 취하고, 한 method (applyPositionChange)를 호출하여 이것을 할 것이다. 그 method는 단일 contact를 해결하는데 있어서 섹션 14.3의 알고리즘을 포함한다.
우리는 이것은 prepareContacts와 같은 방식으로 한다:
이것은 작동할 것이지만, 최적은 아니다.
그림 14.11 이러한 interpenetrating contacts를 한 row로 보여준다. 그 그림의 중간 부분은 contact가 그 순서로 해결될 때 무엇이 발생하는지를 보여준다: 큰 interpenetration이 남아있게 된다. 그 그림의 마지막 부분은 반대 순서로 해결하고 난 후의 contacts의 같은 집합을 보여준다. 여전히 보이는 몇 가지 interpenetration이 있지만, 급격히 줄어들었다.
contacts를 순서대로 가고, 그것들의 interpenetration을 해결하기 보다는, 우리는 penetration order로 충돌을 해결할 수 있다. 각 iteration에서, 우리는 가장 깊은 penetrationvalue를 가진 충돌을 발견하기 위해 탐색한다. 이것은 보통 방식으로 applyPositionChange method를 통해 처리된다. 그 프로세스는 그러고나서 iterations의 몇 가지 maximum number까지 반복된다 (또는 어떤 것이 첫 번째에 오든, 해결될 어떠한 interpenetrations가 없을 때 까지).
이 알고리즘은 같은 contacts를 몇 번 재방문 할 수 있다. 그림 14.12는 flat plane에 resting하고 있는 한 박스를 보여준다. 각 모서리는 그 표면을 관통하고 있다. 첫 번째 모서리를 위로 움직이는 것은 두 번째가 더 내려가게 할 것이다. 그 두 번째를 움직이는 것은 그 첫 번째가 다시 관통하게 할 것이다. 충분한 반복이 주어진다면, 이 상황은 두 개의 코너가 관통하지 않도록 해결될 것이다. 그 iteration limit은 그러나 도달될 가능성이 높다. 만약 너가 실제로 사용되는 iterations의 개수를 체크한다면, 너는 이러한 종류의 상황이 흔하다는 것을 발견하고, 모든 이용가능한 iterations를 소모할 것이다.
그 같은 이슈는 또한 작은 penetration을 가진 한 contact가 결코 해결되지 않는다는 것을 의미할 수 있다: 그 resolution algorithm은 그 contact를 고려하기 전에 iterations가 부족하게 된다. 이 상황을 피하고, 모든 contacts들이 고려되는 것을 보장하기 위해서, 우리는 모든 contacts를 지나는 single pass를 작동시킬 수 있고, 그러고나서 best-first iterative algorithm으로 이동한다. 그러나, 실제로, 이것은 거의 필수적이지 않고, best-first resolution system은 그 자체로 잘 작동한다. 그런데 문제가 fast-moving, tightly packed objects에 대해 발생할 수 있지만; 더 긴 time steps를 가진 시뮬레이션에 대해; 또는 iterations의 회수가 매우 작을 때.
일반적으로 점진적으로 표면에 가라않고 그러고나서 갑자기 점프하는 오브젝트들은 shallwo contacts에 다가가지 못하는 penetration resolution의 증상을 보인다 (쯕, contacts들이 그것들이 깊게 잠길 때 까지 무시되고, 거기에서 그것들은 갑자기 resolved된다). 만약 이것이발생한다면, 너는 그 iterative algorithm 전에 모든 contacts를 한 pass에 추가할 수 있다.
Iterative Algorithm Implemented
가장 큰 penetration을 가진 contact를 찾기 위해서, 우리는 간단히 list에 있는 각 contact를 훝어볼 수 있다. 발견된 그 contact는 그러고나서 해결될 수 있다:
이 method는 각 iteration에서, contacts의 전체 list를 훑어본다. 만약 이것이 우리가 필요한 모든 것이였따면, 우리는 contact의 list를 처음에 정렬하여 더 잘할 수 있고, 간단히 차례대로 그것들을 처리할 수 있다.
불행하게도, 그 이전의 알고리즘은 하나의 adjustment가 다른 contacts의 penetration을 바꿀 지도 모른다는 사실을 고려하지 않는다. 그 contact의 penetration data member는 충돌 탐지동안 설정된다. resolution동안의 오브젝트들의 움직임은 다른 contacts의 penetration depth를바꿀수 있다. 우리가 그림 14.9와 14.12에서 보았듯이.
이것을 고려하기 위해서, 우리는 그 알고리즘의 끝에 한 update를 추가할 필요가 있다:
여기에서 updatePenetrations는 각 contact에 대해 그 penetrations를 다시 계산한다. 이 method를 정확히 구현하기 위해서, 우리는 충돌 탐지기로 다시 돌아가서, 모든 contacts를 다시 처리할 필요가 있을 것이다. 한 오브젝트를 penetration 밖으로 움직이는 것은 다른 contact가 전적으로 사라지게 하거나, 이전에 예상되지 않았던 새로운 contacts를 가져오게 할 수 있다. 그림 14.3은 이것을 보여준다.
Updating Penetration Depths
운 좋게도, 좋은 결과를 주고, 우리가 사용할 수 있느 한 근사가 있다. 한 충돌에 대한 관통이 해결될 때, 오직 하나 또는 두 개의 오브젝트들 만이 움직여질 수 있다: 그 충돌에 관련된 하나 또는 두 개의 오브젝트들 말이다. 우리가 이러한 오브젝트들을 움직이는 지점에서 (applyPositionChange method), 우리는 그것들이 linearly and angularly하게 둘 다 얼마나 움직이는지를 안다.
그 penetration을 해결한 후에, 우리는 우리가 각 오브젝트에 적용했던 linear and angular motion을 추적한다. 그러고나서, 우리는 모든 contacts들을체크하고, 또한 어떤 오브젝트에 적용할 contacts들을 찾는다. 오직 이러한 contacts들은 저장된 linear and angular movements를 기반으로 업데이트 된다.
한 contact에대한 update는 우리가 이 챕터에서 몇 번 사용했던 가정을 포함한다: 그 contact에 관련된 유일한 점은 contact point로서 지정된 점이다. 새로운 penetration value를 계산하기 위해서, 우리는 각 오브젝트에 대해 relative contact point의 새로운 위치를 계산한다, 이것은 우리가 적용했던 linear and angular movements를 기반으로 한다. 그 관통 값은 이러한 두 개의 점들의 새로운 위치를 기반으로 조정된다: 만약 그것들이 떨어진다면 (contact normal의 선을 따라서), 그러면 그 관통은 더 적게 될 것이다; 만약 그것들이 겹치게 된다면, 그러면 그 관통은 증가될 거싱다.
만약 한 contact에 있는 그 첫 번째 오브젝트가 바뀐다면, 그러면 그 위치의 업데이트는 이렇게 될 것이다
만약 그 두 번째 오브젝트가 바뀌었다면, 그 코드는 비슷하지만, 그값은 끝에 더해진다:
마지막으로, 우리는 이 업데이트에서 사용하기 위해, applyPositionChange method에서 만들어진 adjustments를 저장하는 몇 가지 메커니즘이 필요하다. 가장 쉬운 방법은 ContactResolver class에 data members를 더하는 것이다.
그 완전한 코드는 이러한 단계들을 합친다: worst penetration을 찾고, 그것을 해결하고, 그러고나서 나머지 contacts를 업데이트 한다. 그 전체 코드는 이렇게 보인다:
14.4.4 Resolving Velocity
penetration을 해결하고, 우리는 우리의 관심을 속도에 돌린다. 이것은다른 물리엔진이 다양해지는 부분이다, 속도를 해결하는 것에 있어서 몇 가지 다르지만 우수한 전략과 함께. 우리는 chapter 18에서 그것들에 대해 돌아갈 것이다.
이 챕터에 대해, 나는 spectrum에서 가장 간단한 것을 목표로한다 : 작동하고 안정적인 velocity resolution system은 가능한 빠르지만, simultaneously하게 다양한 충돌들을 해결하는 것의 복잡성을피한다. 그 알고리즘은 거의 penetration resolution과 동일하다.
그 알고리즘은 iterations에서 작동한다. 각 iteration에서, 그것은 가장 큰 closing velocity를가진 충돌을 발견한다. 만약 한 closing velocity를 갖는 충돌이 없다면, 그러면 그 알고리즘을 종료될 수 있다. 만약 충돌이 있다면, 그러면 그것은 isolation하게 해결된다, 우리가 이 챕터에 처음 세 개의 섹션에서 보았던 방식을 사용해서. 다른 contacts는 그러고나서 만들어졌던 변화를 기반으로 업데이트 된다. 만약 이용가능한 더 많은 velocity iterations가 있다면, 그러면 그 알고리즘은 반복한다.
Updating velocities
이 알고리즘의 penetration version으로부터의 가장 큰 변화는 velocities를 업데이트 하는 것에 대한 방정식에 있다. 이전처럼, 우리는 변경된 한 오브젝트를 가진 그러한 contacts만을 찾기 위해 탐색한다.
만약 그 contact에 있는 그 첫 번째 오브젝트가 바뀌었다면, 그 velocity의 update는 이것처럼 보인다:
두 번째 오브젝트에 대한 대응 되는 코드는 이것처럼 보인다:
그 calculateDesiredDeltaVelocity 함수는 다음으로 구현된다
다시 한번, 두 경우들은 adjusted되는 contact의 첫 번째 또는 두 번째 오브젝트둘 중 하나로부터의 그것들의 adjustment를 취할 수 있어야만 한다.
완전한 code listing은 penetration에 대해 보여진 것과 유사하다, 그래서 나는 그것을 여기에 포함하지 않을 것이다.
14.4.5 Alternative Update Algorithms
나는 배열을 반복적으로 loop를 돌아서 maxima를 찾거나, 조정해야할 부합된 오브젝트들을 찾는 배열을 탐색하는 알고리즘에 대해 타고난 비호감을가지고 있다고 고백해야만한다. 프로그램 한 지 몇년 간 나는 아마도 훨씬 더 좋은 방법이 있다고 의심하는 것을 배웠다. 이러한 red flags 둘다 penetration and velocity resolution 알고리즘에서 나타난다.
나는 이 책을 준비하고, 알고리즘의 이론적인 속도를 상승시킬 대안과 변형을 구현하는데 많은 시간을썼다. 좋은 성능을 제공하는 한 그러한 대안은 contacts의 정렬된 list를 유지하는 것이다. 예증으로, 나는 그것을 여기에서 묘사할 것이다.
contacts의 리스트는 doubly linked list로 구성된다. contact data structure에 두 개의 포인터들을 추가하여: 리스트에서 다음과 이전 contacts들을 가리키는.
penetration resolution algorithm을 예롤들어서 (비록 같은 것이 velocity resolution에 대해 발생할지라도), 우리는 초기에 모든 contacts들을 decreasing penetration 순서로 더블 링크드 리스트에 정렬시킨다.
그 알고리즘의 각 iteration에서, 리스트에 있는 첫 번째 contact는 선택되고, 해결된다 (그것이 가장 큰 penetration을 가질 것이다). 이제우리는 영향 받을지도 모르는 contacts의 penetrations를 업데이트할 필요가 있다. 이것을 하기 위해서, 나는 contact data structure에서 링크드 리스트의 다른 쌍을 사용한다. 이러한 링크드 리스트들은 한 특정한 오브젝트들을 포함하는 모든 contacts들을 포함한다. 두 개의 그러한 리스트들이 있어야만 한다. 왜냐하면 각 contact는 관련된 두 개의 오브젝트들까지 있기 때문이다. 이러한 리스트들의 start를 유지하기 위해, 나는 RigidBody class에 한 포인터를 추가한다.
이것은 만약 우리가 어떤 rigid bodies가 adjusted되어야 하는지 안다면, 우리가 간단히 그 업데이트를 수행할 contacts의 그것들의 list를 walk할 수 있다는 것을 의미한다. (매우 축약된) 코드에서, 그것은 이것처럼 보인다:
이 지점에서, 우리는 penetration values가 변화한 contacts들의 한 집합을 갖는다. 한 contact는 그것이 resolved 되었기 때문에 변화한다, 그리고 가급적 다른 contacts들의 전체 집합은 resolution의 결과로서 변화된다. 이러한 것들 모든 것은 이제 ordered list에서 잘못된 위치에 있을지도 모른다. 이 알고리즘의 마지막 단계는 그것들의 위치를 adjust하는 것이다.
이것을 하는 가장 쉬운 방법은 그것들을 main ordered list로부터 그것들을 추출하는 것이다. 그것들을 새로운 sublist로서 정렬하고, 그러고나서 main list를 통해 walk하고, 정확한 지점에 순서대로 그것들을 넣는다. 축약된 코드에서, 이것은 다음 처럼 보인다:
나는 standard sorting과 list manipulation routines가 간결함을 위해서 실제 업데이트를 숨기기 위해 사용한 몇 가지 추가 methods들을 따라서 이용가능하다고 가정했다.
Performance
이 종류의 ordering system의 수십개의 변형들이 있고, 정렬하고 리스트를 유지하고 업데이트를 수행할 많은 다른 방법들이 있다. 나는 6개의 다른 방법들을 구현했었다. 이 책을 실험하면서.
가장 좋은 성능 이득은 묘사된 방법을 사용해서 얻어진다. 불행하게도, 그것은 매우 작았따. 몇 가지 contacts들이 있는 프레임들에 대해, 그리고 chapter 16에서 설명된 좀 더 일반적인 최적화를 사용해서, 링크드 리스트 버전의 성능은 상당히 naive approach보다 더 악화된다. 많은 몇 십개의 contacts과 함께, 꽉 packed objects들의 한 집합 중에서, 그것은 좀 더 효율적이게 된다. tightly packed objects중에서, 몇 백개의 contacts들에 대해, 그것은 상당히 더 빠르게 된다.
나는 내가 관련된 게임에서 우연히 만난 시뮬레이션에 대해, 그것은 간단히 extra development effort는 가치가 있지 않다. 나는 오히려 contact와 rigid-body data structures에 있는 추가 포인터를 갖지 않을 것이다. 너는 너가 작업하고 있는 physics의 스케일이 필수적이게 되는 상황을 만날지도 모른다. 게임 개발에 있는 어떤 것에대해, 그것을 최적화 하기전에 너의 코드를 profile 하는 것이 필수적이다.
14.5 Summary
Collision resolution은 우리가 이제까지 만났던 몇 가지 복잡한 수학을 포함한다. 한 single contact에 대해서, 우리는 두 단계로 그것을 한다: 오브젝트 사이의 interpenetration을 해결하고, 그것들의 closing velocity를 rebounding velocity로 바꾸는 것.
그 velocity resolution algorithm은 contact point에 한 impulse 적용하는 것의 효과를 처리하는 것을 포함한다. 이것은 그러고나서 desired effect를 생성할 impulse를 처리하기 위해 사용될 수 있다. 그 결과는 관련된 각 오브젝트의 linear and angular velocity 둘 다를 수정하는 단일 impulse value이다.
velocity resolution algorithm과 다르게, penetration resolution은 physical process에 대응되지 않는다 (rigid objects가 현실에서 관통할 수 없기 때문이다). 이 때문에, 시각적으로 믿을만한 행동을 얻는 많은 다른 접근법들이 있다. 이 챕터에서, 우리는 같은 compression velocity resolution을 위해 사용되는 impulse mathematics로부터 유도된 한 접근법을 구현했다.
one contact를 홀로 해결하는 것은 그리 유용하지 않다. contacts의 완전한 집합을 해결하기 위해서, 우리는 두 가지 유사한 알고리즘들을 사용했었다: 모든 관통들을 해결하는 것과, 모든 velocities를 resolve하는 두 번째 것. 각 알고리즘은 그것들의 severity 순서로 충돌을 고려했다 (즉, penetration depth or closing velocity). 그 worst collision은 isolation에서 해결되고, 그러고나서 영향 받을 다른 충돌들은 업데이트 된다. 각 아고리즘은 고정된 반복의 최대 횟수 까지 계속한다.
그 최종 물리 시스템은 쓸 만하고, 만약 너가 너 자신의 코드를 따라서 쓴다면, 나는 너가 데모 프로그램을 만들어서 결과를 보기를 추천한다. 그 시뮬레이션은 마찰이 없다. 그래서 오브젝트들을 서로에게 미끄러진다. 오브젝트들의 간단한 집합들에 대해선, 잘 작동할 가능성이 높다. 좀 더 복잡한 시나리오들에 대해, 너는 vibrating objects 또는 slow performance의 문제를 눈치챌 지도 모른다.
우리는 이러한 두 챕터들에서 이러한 세 개의 제한을 다룰 것이다. Chapter 15는 우리가 이제까지 다룬 충돌 사이의 차이점을 본다. resting contacts함께 (이것은 vibration problem의 문제의 일부이다). 그것은 또한 friction을 도입한다. 마찰이 도입되기만 한다면, 좀 더 안정성 문제들이 보이게 된다. Chapter 16은 vibration과 friction stability를 다루고, 극적으로 엔진의 성능을 개선시키기 위한 몇 가지 간단한 기법을 본다.
그림 14.10은 도식으로 나타낸 collision resolution process 보여준다. 충돌들은 관련된 오브젝트의 충돌 geometry를 기반으로 충돌 탐지기에 의해 생성된다. 이러한 충돌들은 collision resolution routine으로 넘겨진다, 관련된 오브젝트들에 대한 rigid-body data와 함께.
그 collision resolution routine은 두 개의 컴포넌트들을 갖는다: velocity resolution system and a penetration resolution system. 이러한 것은 이 챕터의 대다수를 구성하는 두 개의알고리즘과 대응된다.
이러한 두 단계들은 서로 독립적이다. 그 오브젝트들의 속도를 바꾸는 것은 그것들이얼마나 관통해 있는지에 영향을 미치지 않고, 역으로도 그렇다.
모든 충돌들이 동시에 처리되어 매우 정교한 velocity resolution을 하는 물리엔진들은 종종 우리가 이전에 구현했던 알고리즘을 사용하는 separate penetration resolver를 가진다.
이 챕터에서 우리가 구현하는 그 collision resolver는 한 클래스에 설정된다: CollisionResolver. 그것은 전체 collisions의 집합과 duration of the frame을 취하는 resolveContacts method를 가지고 있고, 그래서 그것은 세 단계로 그 resolution을 수행한다: 처음에 그것은 각 contact에 대한 internal data를 계산한다; 그러고나서 그것은 그 contacts들을 penetration resolver로 넘긴다; 그러고나서 그것들은 velocity resolver로 넘겨진다:
우리는 또한 contact data structure에 friend declaration을 추가한다. 이것은 resolver가 그것의 internals에 대해 직접적인 접근을 할 수 이게 하기 위해서이다.
14.4.2 Preparing Contact Data
우리는 각 contact에 대해 velocity resolution step뿐만 아니라, penetration resolution step을 수행할 것이기 때문에, 두 단계가 하나의 central location에서 필요한 정보를 계산하는 것이 유용하다. 게다가, resolution의 올바른 순서를 처리하는데 요구되는 추가 정보가 계산되어야만 한다.
그 첫 번째 카테고리는 data의 two bits 이다:
- contact point에 대한 basis matrix인데, contactToWorld라고 불려지는, calculateContactBasis method에서 계산된다.
- contact의 position은각 오브젝트에 대해 상대적이다. 나는 이것은 이전의 코드에서 relativeContactPosition이라고 불렀다.
contact point에서 relative velocity는 second category로 간다. 우리는 velocity를 resolve하기 위해 이것이 필요하다. 그래서 우리는 적절한 방법으로 그것을 계산할 수 있다. 만약 우리가 심각함의 순서로 (가장 빠른게 먼저) 충돌을 해결하려 한다면, 우리는 어떤 충돌이 먼저 고려되어야 할 지를 결정하기 위해 이 값이 필요할 것이다. 그래서 그것은 한 번 계산되는 것으로부터 이익을 얻고, 필요할 때 재사용된다.
우리는 이러한 데이터를 Contact data structure에 저장할 수 있다:
그 preparation routine은 오직 각 contact를 차례로 호출할 필요가 있고, 그것에게 적절한 데이터를 계산하라고 요청한다.
contact의 calculateInternals method에서, 우리는 이러한 데이터들 각각을 계산할 필요가 있다: contact basis, relative position, relative velocity:
그 contact basis method는 section 14.2.1에 묘사되어있다. 그 relative position calculate은 오히려 간단하다. 나머지 두 components - swapping bodies and calculating relative velocity - 는 몇 가지 주석이 필요하다.
Swapping Bodies
그 첫 번째 두 라인들은 만약 충돌에 오직 하나의 오브젝트만 있다면, 그것이 그 배열의 zero position에 있도록 하는 것을 보장한다. 지금까지 우리는 이것이 사실이라고 가정했다. 만약 너의 충돌 탐지기가 이 방식으로 단일 오브젝트의 충돌만을 반환하도록 보장된다면, 그러면 너는 이 코드를 무시할 수 있다.
bodies를 swap하기 위해, 우리는 두 개의 body reference를 바꿀 필요가 있고, 또한 그 contact normal의 방향을 반대로 할 필요가 있다. 그 contact normal은 항상 그 첫 번째 오브젝트의 관점에서 주어진다. 만약 그 bodies가 swapped된다면, 그러면 이것이 바뀔 필요가 있다.
Calculating Relative Velocity
우리가 관심이 있는 relative velocity는 contact point에서 두 오브젝트들의 closing velocity이다. 이것은 오브젝트가 bounce한 후에, desired final velocity를 처리하기 위해 사용될 것이다.
그 velocity는 contact coordinates에서 주어질 필요가 있다. 그것의 x값은 contact normal 방향에서의 속도를 줄 것이다. 그리고 그것의 y와 z값은 contact가 발생하는 sliding의 양을 줄 것이다. 우리는 우리가 friction을 만날 때, 이 두 값을 사용할 것이다.
우리가 보았듯이, 한 점에서의 속도는 두 개의 linear and angular components로 구성된다:
여기에서 q_rel은 우리가 관심있는 그 점의 위치이다. 이것은 object의 center of mass를 기준으로 한다; p는 그 오브젝트의 center of mass의 위치이다 (즉, dot(p)는 그 전체 오브젝트의 linear velocity이다); 그리고 dot(theta)는 그 오브젝트의 angular velocity이다.
contact coordinates에서 그 속도를 계산하기 위해, 우리는 이 방정식을 사용하고, 그러고나서 그 결과를 contact basis matrix의 transpose로 변환한다:
그 calculateInternals method는 contact point에서 전체 closing velocity를 찾는다, second body의 closing velocity를 첫 번째로부터 빼서.
이 알고리즘은 contact basis matrix와 relative contact positions을 사용하기 때문에, 그것은 마지막에 되어야만 한다.
14.4.3 Resolving Penetration
우리는 각 contact를 방문했고, 두 개의 resolution step에 필요할 그 데이터를 계산했다. 우리는 이제 우리의 주의를 모든 contacts에 대한 interpenetration을 해결하는데 돌린다. 우리는 각 contact를 취하고, 한 method (applyPositionChange)를 호출하여 이것을 할 것이다. 그 method는 단일 contact를 해결하는데 있어서 섹션 14.3의 알고리즘을 포함한다.
우리는 이것은 prepareContacts와 같은 방식으로 한다:
이것은 작동할 것이지만, 최적은 아니다.
그림 14.11 이러한 interpenetrating contacts를 한 row로 보여준다. 그 그림의 중간 부분은 contact가 그 순서로 해결될 때 무엇이 발생하는지를 보여준다: 큰 interpenetration이 남아있게 된다. 그 그림의 마지막 부분은 반대 순서로 해결하고 난 후의 contacts의 같은 집합을 보여준다. 여전히 보이는 몇 가지 interpenetration이 있지만, 급격히 줄어들었다.
contacts를 순서대로 가고, 그것들의 interpenetration을 해결하기 보다는, 우리는 penetration order로 충돌을 해결할 수 있다. 각 iteration에서, 우리는 가장 깊은 penetrationvalue를 가진 충돌을 발견하기 위해 탐색한다. 이것은 보통 방식으로 applyPositionChange method를 통해 처리된다. 그 프로세스는 그러고나서 iterations의 몇 가지 maximum number까지 반복된다 (또는 어떤 것이 첫 번째에 오든, 해결될 어떠한 interpenetrations가 없을 때 까지).
이 알고리즘은 같은 contacts를 몇 번 재방문 할 수 있다. 그림 14.12는 flat plane에 resting하고 있는 한 박스를 보여준다. 각 모서리는 그 표면을 관통하고 있다. 첫 번째 모서리를 위로 움직이는 것은 두 번째가 더 내려가게 할 것이다. 그 두 번째를 움직이는 것은 그 첫 번째가 다시 관통하게 할 것이다. 충분한 반복이 주어진다면, 이 상황은 두 개의 코너가 관통하지 않도록 해결될 것이다. 그 iteration limit은 그러나 도달될 가능성이 높다. 만약 너가 실제로 사용되는 iterations의 개수를 체크한다면, 너는 이러한 종류의 상황이 흔하다는 것을 발견하고, 모든 이용가능한 iterations를 소모할 것이다.
그 같은 이슈는 또한 작은 penetration을 가진 한 contact가 결코 해결되지 않는다는 것을 의미할 수 있다: 그 resolution algorithm은 그 contact를 고려하기 전에 iterations가 부족하게 된다. 이 상황을 피하고, 모든 contacts들이 고려되는 것을 보장하기 위해서, 우리는 모든 contacts를 지나는 single pass를 작동시킬 수 있고, 그러고나서 best-first iterative algorithm으로 이동한다. 그러나, 실제로, 이것은 거의 필수적이지 않고, best-first resolution system은 그 자체로 잘 작동한다. 그런데 문제가 fast-moving, tightly packed objects에 대해 발생할 수 있지만; 더 긴 time steps를 가진 시뮬레이션에 대해; 또는 iterations의 회수가 매우 작을 때.
일반적으로 점진적으로 표면에 가라않고 그러고나서 갑자기 점프하는 오브젝트들은 shallwo contacts에 다가가지 못하는 penetration resolution의 증상을 보인다 (쯕, contacts들이 그것들이 깊게 잠길 때 까지 무시되고, 거기에서 그것들은 갑자기 resolved된다). 만약 이것이발생한다면, 너는 그 iterative algorithm 전에 모든 contacts를 한 pass에 추가할 수 있다.
Iterative Algorithm Implemented
가장 큰 penetration을 가진 contact를 찾기 위해서, 우리는 간단히 list에 있는 각 contact를 훝어볼 수 있다. 발견된 그 contact는 그러고나서 해결될 수 있다:
Contact* lastContact = contacts + numContacts; for(unsigned i = 0; i < positionIterations; ++i) { Contact* worstContact = NULL; real worstPenetration = 0; for(Contact* contact = contacts; contact < lastContat; ++contact) { if(contact->penetration > worstPenetration) { worstContact = contact; worstPenetration = contact->penetration; } } if(worstContact) worstContact->applyPositionChange(); else break; }
이 method는 각 iteration에서, contacts의 전체 list를 훑어본다. 만약 이것이 우리가 필요한 모든 것이였따면, 우리는 contact의 list를 처음에 정렬하여 더 잘할 수 있고, 간단히 차례대로 그것들을 처리할 수 있다.
불행하게도, 그 이전의 알고리즘은 하나의 adjustment가 다른 contacts의 penetration을 바꿀 지도 모른다는 사실을 고려하지 않는다. 그 contact의 penetration data member는 충돌 탐지동안 설정된다. resolution동안의 오브젝트들의 움직임은 다른 contacts의 penetration depth를바꿀수 있다. 우리가 그림 14.9와 14.12에서 보았듯이.
이것을 고려하기 위해서, 우리는 그 알고리즘의 끝에 한 update를 추가할 필요가 있다:
Contact* lastContact = contacts + numContacts; for(unsigned i = 0; i < positionIterations; ++i) { // Find worstContact (as before) ... if(!worstContact) break; worstContact->applyPositionChange(); updatePenetrations(); }
여기에서 updatePenetrations는 각 contact에 대해 그 penetrations를 다시 계산한다. 이 method를 정확히 구현하기 위해서, 우리는 충돌 탐지기로 다시 돌아가서, 모든 contacts를 다시 처리할 필요가 있을 것이다. 한 오브젝트를 penetration 밖으로 움직이는 것은 다른 contact가 전적으로 사라지게 하거나, 이전에 예상되지 않았던 새로운 contacts를 가져오게 할 수 있다. 그림 14.3은 이것을 보여준다.
Updating Penetration Depths
운 좋게도, 좋은 결과를 주고, 우리가 사용할 수 있느 한 근사가 있다. 한 충돌에 대한 관통이 해결될 때, 오직 하나 또는 두 개의 오브젝트들 만이 움직여질 수 있다: 그 충돌에 관련된 하나 또는 두 개의 오브젝트들 말이다. 우리가 이러한 오브젝트들을 움직이는 지점에서 (applyPositionChange method), 우리는 그것들이 linearly and angularly하게 둘 다 얼마나 움직이는지를 안다.
그 penetration을 해결한 후에, 우리는 우리가 각 오브젝트에 적용했던 linear and angular motion을 추적한다. 그러고나서, 우리는 모든 contacts들을체크하고, 또한 어떤 오브젝트에 적용할 contacts들을 찾는다. 오직 이러한 contacts들은 저장된 linear and angular movements를 기반으로 업데이트 된다.
한 contact에대한 update는 우리가 이 챕터에서 몇 번 사용했던 가정을 포함한다: 그 contact에 관련된 유일한 점은 contact point로서 지정된 점이다. 새로운 penetration value를 계산하기 위해서, 우리는 각 오브젝트에 대해 relative contact point의 새로운 위치를 계산한다, 이것은 우리가 적용했던 linear and angular movements를 기반으로 한다. 그 관통 값은 이러한 두 개의 점들의 새로운 위치를 기반으로 조정된다: 만약 그것들이 떨어진다면 (contact normal의 선을 따라서), 그러면 그 관통은 더 적게 될 것이다; 만약 그것들이 겹치게 된다면, 그러면 그 관통은 증가될 거싱다.
만약 한 contact에 있는 그 첫 번째 오브젝트가 바뀐다면, 그러면 그 위치의 업데이트는 이렇게 될 것이다
cp = glm::cross(rotationChange[0], c[i].relativeContactPosition[0]); cp += velocityChange[0]; c[i].penetration -= rotationAmount[0] * glm::dot(cp, c[i].contactNormal);
만약 그 두 번째 오브젝트가 바뀌었다면, 그 코드는 비슷하지만, 그값은 끝에 더해진다:
cp = glm::cross(rotationChange[1], c[i].realtiveContactPosition[1]); cp += velocityChange[1]; c[i].penetration += rotationAmount[1] * glm::dot(cp, c[i].contactNormal);
마지막으로, 우리는 이 업데이트에서 사용하기 위해, applyPositionChange method에서 만들어진 adjustments를 저장하는 몇 가지 메커니즘이 필요하다. 가장 쉬운 방법은 ContactResolver class에 data members를 더하는 것이다.
그 완전한 코드는 이러한 단계들을 합친다: worst penetration을 찾고, 그것을 해결하고, 그러고나서 나머지 contacts를 업데이트 한다. 그 전체 코드는 이렇게 보인다:
14.4.4 Resolving Velocity
penetration을 해결하고, 우리는 우리의 관심을 속도에 돌린다. 이것은다른 물리엔진이 다양해지는 부분이다, 속도를 해결하는 것에 있어서 몇 가지 다르지만 우수한 전략과 함께. 우리는 chapter 18에서 그것들에 대해 돌아갈 것이다.
이 챕터에 대해, 나는 spectrum에서 가장 간단한 것을 목표로한다 : 작동하고 안정적인 velocity resolution system은 가능한 빠르지만, simultaneously하게 다양한 충돌들을 해결하는 것의 복잡성을피한다. 그 알고리즘은 거의 penetration resolution과 동일하다.
그 알고리즘은 iterations에서 작동한다. 각 iteration에서, 그것은 가장 큰 closing velocity를가진 충돌을 발견한다. 만약 한 closing velocity를 갖는 충돌이 없다면, 그러면 그 알고리즘을 종료될 수 있다. 만약 충돌이 있다면, 그러면 그것은 isolation하게 해결된다, 우리가 이 챕터에 처음 세 개의 섹션에서 보았던 방식을 사용해서. 다른 contacts는 그러고나서 만들어졌던 변화를 기반으로 업데이트 된다. 만약 이용가능한 더 많은 velocity iterations가 있다면, 그러면 그 알고리즘은 반복한다.
Updating velocities
이 알고리즘의 penetration version으로부터의 가장 큰 변화는 velocities를 업데이트 하는 것에 대한 방정식에 있다. 이전처럼, 우리는 변경된 한 오브젝트를 가진 그러한 contacts만을 찾기 위해 탐색한다.
만약 그 contact에 있는 그 첫 번째 오브젝트가 바뀌었다면, 그 velocity의 update는 이것처럼 보인다:
cp = glm::cross(rotationChange[0], c[i].realtiveContactPosition[0]); cp += velocityChange[0]; c[i].contactVelocity += glm::transpose(c[i].contactToWorld) * cp; c[i].calculateDesiredDeltaVelocity(duration);
두 번째 오브젝트에 대한 대응 되는 코드는 이것처럼 보인다:
cp = glm::cross(rotationChange[0], c[i].realtiveContactPosition[1]); cp += velocityChange[0]; c[i].contactVelocity -= glm::transpose(c[i].contactToWorld) * cp; c[i].calculateDesiredDeltaVelocity(duration);
그 calculateDesiredDeltaVelocity 함수는 다음으로 구현된다
다시 한번, 두 경우들은 adjusted되는 contact의 첫 번째 또는 두 번째 오브젝트둘 중 하나로부터의 그것들의 adjustment를 취할 수 있어야만 한다.
완전한 code listing은 penetration에 대해 보여진 것과 유사하다, 그래서 나는 그것을 여기에 포함하지 않을 것이다.
14.4.5 Alternative Update Algorithms
나는 배열을 반복적으로 loop를 돌아서 maxima를 찾거나, 조정해야할 부합된 오브젝트들을 찾는 배열을 탐색하는 알고리즘에 대해 타고난 비호감을가지고 있다고 고백해야만한다. 프로그램 한 지 몇년 간 나는 아마도 훨씬 더 좋은 방법이 있다고 의심하는 것을 배웠다. 이러한 red flags 둘다 penetration and velocity resolution 알고리즘에서 나타난다.
나는 이 책을 준비하고, 알고리즘의 이론적인 속도를 상승시킬 대안과 변형을 구현하는데 많은 시간을썼다. 좋은 성능을 제공하는 한 그러한 대안은 contacts의 정렬된 list를 유지하는 것이다. 예증으로, 나는 그것을 여기에서 묘사할 것이다.
contacts의 리스트는 doubly linked list로 구성된다. contact data structure에 두 개의 포인터들을 추가하여: 리스트에서 다음과 이전 contacts들을 가리키는.
penetration resolution algorithm을 예롤들어서 (비록 같은 것이 velocity resolution에 대해 발생할지라도), 우리는 초기에 모든 contacts들을 decreasing penetration 순서로 더블 링크드 리스트에 정렬시킨다.
그 알고리즘의 각 iteration에서, 리스트에 있는 첫 번째 contact는 선택되고, 해결된다 (그것이 가장 큰 penetration을 가질 것이다). 이제우리는 영향 받을지도 모르는 contacts의 penetrations를 업데이트할 필요가 있다. 이것을 하기 위해서, 나는 contact data structure에서 링크드 리스트의 다른 쌍을 사용한다. 이러한 링크드 리스트들은 한 특정한 오브젝트들을 포함하는 모든 contacts들을 포함한다. 두 개의 그러한 리스트들이 있어야만 한다. 왜냐하면 각 contact는 관련된 두 개의 오브젝트들까지 있기 때문이다. 이러한 리스트들의 start를 유지하기 위해, 나는 RigidBody class에 한 포인터를 추가한다.
이것은 만약 우리가 어떤 rigid bodies가 adjusted되어야 하는지 안다면, 우리가 간단히 그 업데이트를 수행할 contacts의 그것들의 list를 walk할 수 있다는 것을 의미한다. (매우 축약된) 코드에서, 그것은 이것처럼 보인다:
class Contact { // Holds the doubly linked list pointers for the ordered list. Contact* nextInorder; Contact* previousInOrder; // Holds pointers to the next contact that involves each rigid body Contact* nextObject[2]; // .. Other data as before ... } class RigidBody { // Holds the list of contacts that involve this body. Contact* contacts; // .. Other data as before ... }
이 지점에서, 우리는 penetration values가 변화한 contacts들의 한 집합을 갖는다. 한 contact는 그것이 resolved 되었기 때문에 변화한다, 그리고 가급적 다른 contacts들의 전체 집합은 resolution의 결과로서 변화된다. 이러한 것들 모든 것은 이제 ordered list에서 잘못된 위치에 있을지도 모른다. 이 알고리즘의 마지막 단계는 그것들의 위치를 adjust하는 것이다.
이것을 하는 가장 쉬운 방법은 그것들을 main ordered list로부터 그것들을 추출하는 것이다. 그것들을 새로운 sublist로서 정렬하고, 그러고나서 main list를 통해 walk하고, 정확한 지점에 순서대로 그것들을 넣는다. 축약된 코드에서, 이것은 다음 처럼 보인다:
Contact* adjustedList; Contact* orderedList; orderedList = sort(contacts); for(unsigned i = 0; i < positionIterations; ++i) { // Make sure the worst contact is bad. if(orderedList->penetration < 0) break; // Adjust its position. orderedList->applyPositionChange(); // Move it to the adjusted list. moveToAdjusted(orderedList); // Loop through the contacts for the first body. Contact* bodyContact = orderedList->body[0].contacts; while(bodyContact) { // Update the contact. bodyContact->updatePenetration(positionChange, orientationChange); // Schedule it for adjustment. moveToAdjusted(bodyContact); // Find out which linked list to move along on, then follow // it to get the next contact for this body. unsigned index = 0; if(bodyContact->body[0] != orderedList->body[0]) index = 1; bodyContact = bodyContact->nextObject[index]; } if(orderedList->body[1]) { // Do the same thing for the second body // (omitted for brevity). } // Now sort the adjusted set sortInPlace(adjustedList); // And insert them at the correct place. Contact* orderedListEntry = orderedList; while(orderedListEntry) { if(adjustedList->penetration > orderedListEntry->penetration) { Contact* contactToInsert = adjustedList; adjustedList = adjustedList->nextInOrder; insertIntoList(contactToInsert, orderedListEntry); } } }
나는 standard sorting과 list manipulation routines가 간결함을 위해서 실제 업데이트를 숨기기 위해 사용한 몇 가지 추가 methods들을 따라서 이용가능하다고 가정했다.
Performance
이 종류의 ordering system의 수십개의 변형들이 있고, 정렬하고 리스트를 유지하고 업데이트를 수행할 많은 다른 방법들이 있다. 나는 6개의 다른 방법들을 구현했었다. 이 책을 실험하면서.
가장 좋은 성능 이득은 묘사된 방법을 사용해서 얻어진다. 불행하게도, 그것은 매우 작았따. 몇 가지 contacts들이 있는 프레임들에 대해, 그리고 chapter 16에서 설명된 좀 더 일반적인 최적화를 사용해서, 링크드 리스트 버전의 성능은 상당히 naive approach보다 더 악화된다. 많은 몇 십개의 contacts과 함께, 꽉 packed objects들의 한 집합 중에서, 그것은 좀 더 효율적이게 된다. tightly packed objects중에서, 몇 백개의 contacts들에 대해, 그것은 상당히 더 빠르게 된다.
나는 내가 관련된 게임에서 우연히 만난 시뮬레이션에 대해, 그것은 간단히 extra development effort는 가치가 있지 않다. 나는 오히려 contact와 rigid-body data structures에 있는 추가 포인터를 갖지 않을 것이다. 너는 너가 작업하고 있는 physics의 스케일이 필수적이게 되는 상황을 만날지도 모른다. 게임 개발에 있는 어떤 것에대해, 그것을 최적화 하기전에 너의 코드를 profile 하는 것이 필수적이다.
14.5 Summary
Collision resolution은 우리가 이제까지 만났던 몇 가지 복잡한 수학을 포함한다. 한 single contact에 대해서, 우리는 두 단계로 그것을 한다: 오브젝트 사이의 interpenetration을 해결하고, 그것들의 closing velocity를 rebounding velocity로 바꾸는 것.
그 velocity resolution algorithm은 contact point에 한 impulse 적용하는 것의 효과를 처리하는 것을 포함한다. 이것은 그러고나서 desired effect를 생성할 impulse를 처리하기 위해 사용될 수 있다. 그 결과는 관련된 각 오브젝트의 linear and angular velocity 둘 다를 수정하는 단일 impulse value이다.
velocity resolution algorithm과 다르게, penetration resolution은 physical process에 대응되지 않는다 (rigid objects가 현실에서 관통할 수 없기 때문이다). 이 때문에, 시각적으로 믿을만한 행동을 얻는 많은 다른 접근법들이 있다. 이 챕터에서, 우리는 같은 compression velocity resolution을 위해 사용되는 impulse mathematics로부터 유도된 한 접근법을 구현했다.
one contact를 홀로 해결하는 것은 그리 유용하지 않다. contacts의 완전한 집합을 해결하기 위해서, 우리는 두 가지 유사한 알고리즘들을 사용했었다: 모든 관통들을 해결하는 것과, 모든 velocities를 resolve하는 두 번째 것. 각 알고리즘은 그것들의 severity 순서로 충돌을 고려했다 (즉, penetration depth or closing velocity). 그 worst collision은 isolation에서 해결되고, 그러고나서 영향 받을 다른 충돌들은 업데이트 된다. 각 아고리즘은 고정된 반복의 최대 횟수 까지 계속한다.
그 최종 물리 시스템은 쓸 만하고, 만약 너가 너 자신의 코드를 따라서 쓴다면, 나는 너가 데모 프로그램을 만들어서 결과를 보기를 추천한다. 그 시뮬레이션은 마찰이 없다. 그래서 오브젝트들을 서로에게 미끄러진다. 오브젝트들의 간단한 집합들에 대해선, 잘 작동할 가능성이 높다. 좀 더 복잡한 시나리오들에 대해, 너는 vibrating objects 또는 slow performance의 문제를 눈치챌 지도 모른다.
우리는 이러한 두 챕터들에서 이러한 세 개의 제한을 다룰 것이다. Chapter 15는 우리가 이제까지 다룬 충돌 사이의 차이점을 본다. resting contacts함께 (이것은 vibration problem의 문제의 일부이다). 그것은 또한 friction을 도입한다. 마찰이 도입되기만 한다면, 좀 더 안정성 문제들이 보이게 된다. Chapter 16은 vibration과 friction stability를 다루고, 극적으로 엔진의 성능을 개선시키기 위한 몇 가지 간단한 기법을 본다.
댓글 없음:
댓글 쓰기