지난 챕터에서, 우리는 force generator로서의 스프링과, 다양한 오브젝트들이 서로 영향 끼치도록 하는 한 방식으로서의 스프링 둘 다를 보았다. 이제 우리가 다른 오브젝트들의 움직임을 기반으로 움직이는 오브젝트들을 가질 첫 번째 시간이다.
스프링들이 많은 상황들을 나타내기 위해 사용될 수 있을지라도, 그것들은 나쁘게 행동할 수 있다. 우리가 오브젝트들이 꽉 연결되도록 하길 원할 때, 우리가 필요한 그 스프링 상수는 실젤적으로 재현하기에 불가능하다. 오브젝트들이 stiff rods에 의해 연결되는 상황 또는 딱딱한 표면에의해 분리된 상황에 대해, 스프링들은 가능한 옵션이 아니다.
이 챕터에서, 나는 hard constraints에 대해 말할 것이다. 초기에, 우리는 가장 흔한 hard constraint를 볼 것이다- 오브젝트 사이의 충돌과 접촉. 같은 수학이 오브젝트들을 함께 연결시키기 위해 사용될 수 있는 rods 또는 unstretchable cables과 같은 다른 종류의 hard constraints에 대해 사용될 수 있다.
우리의 물리 엔진에서 hard constraints를 처리하기 위해, 우리는 force generators의 comfortable world를 떠날 필요가 있다. 우리가 이 책에서 만들고 있는 모든 엔진은 hard constraints를 force generators와 다르게 다룬다. 책의 마지막에서, chapter 18에서, 우리는 간단히 그것들을 모두 하나로 통합하는 대안의 접근법을 간단히 볼 것이다.
7.1 Simple Collision Resolution
hard constraints를 처리하기 위해, 우리는 우리의 엔진에 collision resolution system을 추가할 것이다. 그 책의 이 부분을 위해서, "collision"은 두 오브젝트들이 닿고 있는 어떤 상황을 언급한다. 일반적인 영어에서, 우리는 충돌이 폭력적인 프로세스로 생각한다. 그 프로세스에서 두 오브젝트들은 몇 가지 중요한 closing velocity로 만난다. 우리의 목족을 이ㅜ해서, 이것은 또한 사실이지만, 우연히 닿는 두 오브젝트들은 어떠한 closing velocity가 없는 충돌상태인 것으로 생각되어질 수 있다. 우리가 high-speed 충돌을 해결하기 위해 사용하는 같은 프로세스는 resting contact를 해결하기 위해 사용될 것이다. 이것은 정당화될 필요가 있는 중요한 가정이다. 그래서 나는 이 챕터의 나중에 그것에 돌아올 것이고, 다양한 시점에서, 책에서 말할 것이다. 나중에 용어를 바꾸는 것을 피하기 위해서, 나는 collision과 contact라는 단어를 상호교환하여 이 챕터에서 사용할 것이다.
두 오브젝트가 충돌할 때, 그것들의 충돌 후 움직임은 그것들의 충돌 이전의 움직임으로부터 계산되어질 수 있다: 이것이 collision resolution이다. 우리는 두 오브젝트가 충돌로부터 발생할 정확한 움직임을 갖게하도록 하여 충돌을 해결(resolve)한다. 충돌은 매우 작은 시간의 순간에 발생하기 때문에 (대부분의 오브젝트들에 대해, 우리는 충돌이 발생하는 과정을 볼 수 없다; 그것은 즉각적인 것 처럼 보인다), 우리는 직접가서, 각 오브젝트의 움직임을 조작한다.
7.1.1 The Closing Velocity
충돌하는 몸체들의 움직임을 관장하는 법칙들은 그것들의 closing velocity에 의존한다. closing velocity는 두 오브젝트들이 함께 움직이는 총 스피드이다.
또한 이것이 speed라기보다는 clsoing velocity라는 것에 주목해라. 비록 그것이 스칼라 양일지라도. 스피드(속력, speed)들은 방향이 없다; 그것들은 오직 양의(또는 0) 값을 가질 수 있다. Velocity(속도)는 방향을 가질 수 있다. 만약 우리가 velocities로서 벡터들을 가진다면, 그러면 그 방향을 벡터의 방향이다; 그러나 만약 우리가 스칼라 값을 가진다면, 그러면 그 방향을 그 값의 부호에 의해 주어진다. 서로로부터 떨어져서 움직이는 두 오브젝트들은 예를들어 0 보다 작은 closing velocity를 가질 것이다.
우리는 두 오브젝트의 closing velocity를 한 오브젝트에서 다른 것으로의 방향에서 그것들의 속도의 컴포넌트를 찾아서 계산한다:
여기에서 v_c는 closing velocity이다 (스칼라양), p_a와 p_b는 오브젝트 a와 b의 위치들이다. 그 dot은 스칼라곱이고, widehat(p)는 p와 같은 방향의 단위 벡터이다. 이것은 다음으로 간단하게 되어질 수 있다.
비록 그것이 전통일지라도, 이 양의 부호를 바꾸는 것이 좀 더 흔하다. closing velocity(가까워지는 속도)라기 보다, 우리는 separating velocity(분리되는 속도)를 갖는다. 그 closing velocity는 다른 오브젝트를 기준으로 한 오브젝트의 속도이다. 두 오브젝트 사이의 방향에서.
이 경우에 서로 가까워지고있는 두 오브젝트들은 negative relative velocity를 갖고, 분리되는 오브젝트들의 양의 velocity를 갖는다. 수학적으로 이것은 간단히 7.1 방정식의 부호를 바꾸는 문제이다. 이것은 다음을 준다
여기에서 v_s는 separating velocity이다. 그리고 그것은 우리가 이 책의 나머지에서 사용할 포맷이다. 너는 너가 원하면 closing velocities를 고수할 수 있다: 그것은 선호의 문제이다, 비록 그것을 보상하기 위해 너가 엔진에서 다양한 양의 부호를 바꿔야만 할지라도.
7.1.2 The Coefficient of Restitution
우리가 지난 챕터에서 보았듯이, 두 오브젝트들이충돌할 때, 그들은 함께 압축된다. 그리고 그것들의 표면의 스프링같은 변형이 오브젝트들이 떨어지게 하는 힘이 축적되도록 야기한다. 이것은 모두 시간의 짧은 부분에서 발생한다 (우리에게 너무 빨라서 프레임마다 재현할 수 없다, 비록 매우 high-speed film에서 포착이 될 만큼 충분히 길지라도). 결국, 두 오브젝트들은 더 이상 어떠한 closing velocity도 가지지 않을 것이다. 이 행동이 스프링 같을지라도, 현실에서 더 해야할 것이 있다.
이 압축 동안 모든 이러한 상황의 종류들이 발생할 수 있고, 관련된 물질의 특이성은 매우 복잡한 상호작요이 발생하도록 야기시킬 수 있다. 현실에서, 그 행동은 damped spring의 그것에 순응하지 않고, 우리는 real process의 subtleties를 포착하는 것을 희망할 수 없다.
특히, 스프링 모델은 운동량이 충돌동안 보존된다고 가정한다:
여기에서 m_a는 오브젝트 a의 질량이고, dot{diff{p}_a}는 충돌 전 object a의 속도이고, dot{diff{p}_a}'는 충돌 이후의 속도이다.
운 좋게도, 대다수의 충돌들은 거의 springlike ideal처럼 행동한다. 우리는 운동량의 보존을 가정하여 완벽히 믿을 수 있는 행동을 만들어 낼 수 있다, 그리고 우리는 우리의 충돌을 모델링하기 위해 방정식 7.3을 사용하라것이다.
방정식 7.3은 우리에게 충돌 전 후의 총 속도에 대해 말할 것이지만, 그것은 우리에게 각 오브젝트의 개별 속도들에 대해 말해주지 않는다. 그 개별 속도들은 closing velocity를 사용하여 함께 연결된다. 다음의 방정식을 따라서
(separating velocity, closing velocity의
여기에서 v'_s는 충돌 후의 separating velocity이고, v_s는 충돌 전의 separating velocity이고, c는 "coefficient of restitution (복구계수)"라고 불려지는 상수이다.
복구 계수는 오브젝트가 충돌 후 분리되는 속도를 통제한다. 그것은 충돌하는 물질들에 의존한다. 물질의 다른 쌍들은 다른 계수들을 갖을 것이다. 당구장 공 또는 라켓 위의 테니스 공같은 몇 가지 오브젝트들은 반대로 튄다. 다른 오브젝트들은 충돌 할 때 함께 붙는다 - snowball과 누군가의 얼굴.
만약 그 계수가 1이라면, 그러면 그 오브젝트들은 그것들이 가까워지고 있을 때와 같은 속도로 분리되어 떨어질 것이다. 만약 그 계수가 0이라면, 그러면 그 오브젝트들은 합쳐질 것이고, 함께 움직일 것이다 (즉, 그것들의 분리 속도는 0 이 될 것이다), 복구 계수와 상관없이, 방정식 7.3은 여전히 유효할 것이다: 총 운동량은 같을 것이라는 거다.
두 방정식을 사용하여, 우리는 dot{diff{p}}'_a, dot{diff{p}}'_b에 대한 값을 얻을 수 있다.
7.1.3 The Collision Direction And The Contact Normal
지금까지, 우리는 두 오브젝트들 사이의 충돌의 관점으로 이야기 해왔다. 종종, 우리는 또한 한 오브젝트와 우리가 물리적으로 재현하고 있지 않은 것 사이의 충돌을 지원할 수 있기를 원한다. 이것은 땅, 한 level의 벽들, 또는 다른 움직일 수 없는 오브젝트일지도 모른다. 우리는 이러한 것을 무한한 질량을 가진 오브젝트들로 나타낼 수 있지만, 그것은 시간 낭비일 것이다: 정의에 의해서 그것들은 결코 움직이지 않을 것이다.
만약 우리가 한 오브젝트와 움직일 수 없은 scenery의 어떤 것 사이의 충돌을 갖는다면, 그러면 우리는 각 오브젝트의 위치 사이의 벡터 관점에서, separting velocity를 계산할 수 없다: 우리는 오직 한 오브젝트만 가질 뿐이다. 다시 말해서, 우리는 방정식 7.2에서의 widehat(p_a - p_b) 항을 쓸 수 없다; 우리는 그것을 대체할 필요가 있다.
그 widehat(p_a - p_b)은 우리에게 separating velocity가 발생하는 방향을 준다. 그 separating velocity은 두 오브젝트의 상대 속도의 내적과 이 항으로 계산되어진다. 만약 우리가 두 오브젝트들을 가지고 있지 않다면, 우리는 우리에게 명시적으로 주어질 방향을 물을 수 있다. 그것은 두 오브젝트가 충돌하고 있는 방향이고, 보통 "collision normal" 또는 "contact normal"이라고 불려진다. 그것이 방향이기 때문에, 그 벡터는 항상 1의 크기를 갖는다.
우리가 두 파티클들이 충돌하고 있는 경우에, 그 contact normal은 항상 다음으로 주어질 것이다:
전통적으로, 우리는 오브젝트 a의 관점의 contact normal를 준다. 이 경우에, a의 관점으로부터, 그 contact는 b로부터 들어온다. 그래서 우리는 p_a - p_b를 사용한다. b의 관점으로부터 충돌 방향을 주기 위해서, 우리는 간단히 -1을곱할 수 있다. 실제로, 우리는 명시적으로 이것을 하지 않지만, b의 separting velocity를 계산하기 위해 사용된 코드에 이 변환을 분해하여 넣는다. 너는 그 코드에서, 우리가 이 챕터에서 나중에 구현한 것을 알아 볼 것이다: 마이너스 부호는 b의 계산에서 나타날 것이다.
한 파티클이 땅과 충돌할 때, 우리는 한 오브젝트 a (파티클)과 오브젝트 가 아닌 b를 갖는다. 오브젝트 a의 관점의 경우에, 그 contact normal은 이렇게 될 것이다
이것은 땅이 충돌점에서 level로 가정한다.
우리가 파티클을 떠나서 full rigid bodies로 작업하기 시작할 때, explicit contact normal를갖는 것은 오브젝트 간의 충돌에 있어서 중요하게 된다. 나중 챕터를 먼저하는 거 없이, 그림 7.1은 우리가 우연히 만날지도 모르는 상황을 준다. 여기에서 그것들의 도형의 특성에 의해, 두 충돌하는 오브젝트들은 만약 우리가 그것들의 위치를 고려한다면 우리가 기대하는 것과는 거의 정확히 반대 방향의 contact normal를 갖는다. 그 오브젝트들은 서로 arch이고, 그 접점(contact)는 그것들이 함께 유지하기 보다는 떨어져서 움직이는 것을 막는다. 이 챕터의 마지막에, 우리는 파티클에 대해 유사한 상황을 볼 것이다. 그리고 이것은 rods와 다른 stiff connections을 나타내기 위해 사용될 수 있다.
정확한 contact normal로 방정식 7.2은 다음이 된다.
7.1.4 Impulses
우리가 충돌을 해결하기 위해 만들어야 하는 변화는 오직 속도에서의 변화이다. 지금까지 물리 엔진에서,우리는 가속도를 사용하여 속도에 대한 변화만을 만들어 왔다. 만약 그 가속도가 오랜 시간 적용된다면, 속도에 더 큰 변화가 있을 것이다. 여기에서 그 변화는 즉각적이다: 그 속도는 즉시 새로운 값을 취한다는 것이다.
힘을 적용하는 것이 한 오브젝트의 가속도를 바꾼다는 것을 상기해라. 만약 우리가 즉시 그 힘을 바꾼다면, 그 가속도는 즉시 또한 바뀐다. 우리는 유사한 방식으로 그것의 속도를 바꾸기 위해 한 오브젝트에 작용하는 것을 생각할 수 있다. 힘이라기 보다, 이것은 "impulse(충격)"이라고 불려진다: 속도에서의 즉각적인 변화라는것이다. 같은 방식으로 우리는 힘에 대해 다음을 갖는다
우리는 충격에 대해 다음을 갖는다
여기에서 g는 충격이다. 충격은 종종 글자 p로 쓰여진다; 나는 object의 위치 p와의혼동을 피하기 위해 g를 사용할 것이다.
그러나 힘과 충격 사이의 중대한 차이가 있다. 한 오브젝트는 그것이 힘에 의해 작용받지 않는다면, 가속도를 갖지 않는다: 우리는 D'Alembert의 원리를 사용하여 힘을 합쳐서 총 가속도를 처리할 수 있었다. 반면에, 한 오브젝트는 비록 어떠한 충격(또는 힘)이 그것에 작용하지 않을지라도, 계속해서 속도를 가질 것이다. 그러므로 그 충격은 속도를 변화시킨다; 그것은 완전히 속도에 책임이 있는것이 아니다. 우리는 D'Alembert의 원리를 이용하여 충격을 합칠 수 있지만, 그 결과는 속도에서의 총 변화가 될 것이다. 총 속도가 아닌:
여기에서 g_1, ... , g_n은 그 오브젝트에 작용하는 모든 충격의 집합이다. 실제로, 우리는 힘을 축적하는 방식으로 충격을 축적하지 않을 것이다. 우리는 충격을 collision resolution process 동안에 발생할 때 적용할 것이다. 각각은 다음의 방정식을 사용하여 하나씩 적용될 것이다
우리의 collision resolution의 결과는 각 오브젝트에 적용할 충격일 것이다. 그 충격은 즉시 적용될 것이고, 즉시 그 오브젝트의 속도를 바꿀 것이다.
7.2. Collision Processing
충돌을 처리하기 위해서, 우리는 새로운 코드를 작성할 것이다 - ContactResolver. 그것은 전체 충돌의 집합을 취하고, 연관된 오브젝트들에 관련된 충격을 적용하는 일을 가진다. 각 충돌은 Contact data structrue에서 제공되고, 이것처럼 보인다:
/** * A contact represents two objects in contact (in this case * ParticleContact representing two particles). Resolving a * contact removes their interpenetration, and applies sufficient * impulse to keep them apart. Colliding bodies may also rebound. * The contact has no callable functions, it just holds the * contact details. To resolve a set of contacts, use the particle * contact resolver class. */ class ParticleContact { public: /** * Holds the particles that are involved in the contact. The * second of these can be NULL, for contacts with the scenery. */ GPEDParticle* particle[2]; /** * Holds the normal restitution coefficient at the contact. */ real restitution; /** * Holds the direction of the contact in world coordinates. */ glm::vec3 contactNormal; };
그 구조는 충돌에 연관된 각 오브젝트에 대한 포인터를 유지한다; 첫 번째 오브젝트의 관점의 contact normal를 나타내는 벡터; 그리고 contact에 대한 복구 계수에 대한 데이터 멤버. 만약 우리가 한 오브젝트와 scenery사이의 충돌을 다룬다면 (즉, 오직 한 오브젝트만 연관된다), 그러면 그 두 번째 오브젝트에 대한 포인터는 NULL이 될 것이다.
한 contact(접촉)을 해결하기 위해, 우리는 그 섹션에서 이전의 충돌 방정식을 구현한다. 이것은 다음을 준다
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | void GPED::ParticleContact::resolve(real duration) { resolveVelocity(duration); } real GPED::ParticleContact::calculateSeparatingVelocity() const { glm::vec3 relativeVelocity = particle[0]->getVelocity(); if (particle[1]) relativeVelocity -= particle[1]->getVelocity(); return glm::dot(relativeVelocity, contactNormal); } void GPED::ParticleContact::resolveVelocity(real duration) { // Find the velocity in the direction of the contact real separatingVelocity = calculateSeparatingVelocity(); // Check whether it needs to be resolved if (separatingVelocity > 0) { // The contact is either separating or stationary - there's // no implies required. return; } // Calculate the new separating velocity. real newSepVelocity = -separatingVelocity * restitution; real deltaVelocity = newSepVelocity - separatingVelocity; // We apply the change in velocity to each object in proportion to // its inverse mass (i.e., those with lower inverse mass [higher // actual mass] get less change in velocity). real totalInverseMass = particle[0]->getInverseMass(); if (particle[1]) totalInverseMass += particle[1]->getInverseMass(); // If all particles have infinite mass, then impulses have no effect. if (totalInverseMass <= 0) return; // Calculate the impulses to apply real impulse = deltaVelocity / totalInverseMass; // Find the amount of impulse per unit of inverse mass. glm::vec3 impulsePerIMass = contactNormal * impulse; // Apply impulses: they are appliedin the direction of the contact, // and are proportional to the inverse mass. particle[0]->setVelocity ( particle[0]->getVelocity() + impulsePerIMass * particle[0]->getInverseMass() ); if (particle[1]) { // Particle 1 goes in the opposite direction particle[1]->setVelocity ( particle[1]->getVelocity() + impulsePerIMass * -particle[0]->getInverseMass() ); } } |
글쓴이는 정확히 충격을 어떻게 계산하는지 안알려줬는데 코드에서는
복구계수를 이용하여 v'_s 를 구하고, 그 새로운 분리속도와, 현재 분리속도의 차이를 이용하여 충격을구한다. 그리고 total InverseMass로 나누어, 충격양을 더 잘 계산한다. 그래야 부딪힌 질량을 고려하여, 다시 속도를 변경할 때, 그 오브젝트의 질량을 고려해서 할 수 있기 때문이다.
}
이것은 직접적으로 충돌을 반영하기 위해, 각 오브젝트의 속도를 변경한다.
7.2.1 Collision Detection
충돌점들은 보통 collision detector를 사용하여 발견될 것이다. 충돌 탐지기는 충돌하는 오브젝트들의 쌍들을 또는 몇 가지 움직일 수 없는 scenery와 충돌하는 단일의 오브젝트들을 찾는데 책임이 있는 코드의 한 덩어리이다.
우리의 엔진에서, 충돌 탐지 알고리즘의 최종 결과는 Contact data structures의 한 집합인데, 그것은 적절한 정보로 채워진다. 충돌 탐지는 명백히 오브젝트의 도형을 고려할 필요가 있다: 그것들의 모양과 크기. 물리엔진에서 지금까지, 우리는 우리가 파티클들을다루고 있다고 가정했다. 그리고 이것은 우리가 도형을 고려하는 것을 피하게 해주었다.
이것은 우리가 좀 더 복잡한 3D 오브젝트들을 가지고 할 때에도 유지할 구분이다: physics simulation system (운동 법칙, collision resolution, 힘들을 다루는 엔진의 부분)은 그것이 다루고있는 오브젝트들의 모양의 세부사항을 알 필요가 없을 것이다. 충돌 탐지 시스템은 두 오브젝트들이 언제 그리고 어디에서 닿고있는지, 그리고 그것들 사이의 contact normal과 같은 기하적인 어떤 특성을 계산하는데 책임이 있다.
contact points를 처리하기 위해 사용되는 광범위한 알고리즘이 있고, 우리는 chapter 12에서 full 3D objects를 위한 유용한 collision detection routines의 한 범위를 구현할 것이다. 지금은, 우리는 이것이 black box안에 숨겨진 magic process라고 가정할 것이다.
한 가지 예외가 있다: 나는 다음 챕터에서, 작은 구로서 나타내어지는 파티클들을 위한 가장 간단하고 가능한 collision detection을 다룰 것이다. 이것은 우리가 우리가 구성할 mass-aggregate engine을 가진 몇 가지 유용한 물리 시스템을 구성하는 것을 허용할 것이다. 그것을 제외하고, 나는 chapter 10에서 full 3D rigid bodies를 볼 때까지는 그 세부사항을 남겨둘 것이다.
몇 가지 충돌 탐지 알고리즘은 오브젝트들이 움직이는 방식을 고려할 수 있고, 미래에 일어날 수 있는 충돌들을 예측하려 노력한다. 대부분의 것들은 간단히 그 오브젝트들의 집합을 보고, 어떤 두 오브젝트들이 관통하는지를 보기 위해 체크한다.
두 오브젝트들은 만약 그것들이 부분적으로 서로 삽입되었다면, 관통하고 있는 것이다. 그림 7.2에서 보여지듯이. 우리가 부분적으로 삽입된 오브젝트들 사이의 충돌을 처리할 때, 그것들의 속도만을 변화시키는 것은 충분하지 않다. 만약 그 오브젝트들이 작은 복구 계수를 가지고 충돌한다면, 그것들의 separation velocity는 거의 0이 될 것이다. 이 경우에 그것들은 떨어져서 움직이지 않을 것이고, 플레이어는 그 오브젝트들이 불가능한 방식으로 붙어있는 것을 볼 것이다.
collisions을 해결하는 부분으로서, 우리는 그 interpenetration을 해결할필요가 있다.
7.2.2 Resolving Interpenetration
두 오브젝트들이 서로 관통할 때, 우리는 그것들이 그것들을 충분히 분리할만큼 분리하여 움직이게 한다. 우리는 collision detector가 우리에게 그 오브젝트들이 얼마나 많이 관통되었는지를 말하기를 기대한다. 그것이 만드는 Contact data structure의 일부로서. 그 관통 깊이의 calculation은 충돌하는 오브젝트들의 도형에 의존한다. 그래서 우리는 이전에 보았듯이, 이것은 physics simulator라기 보다는 collision detection system의 영역이다.
우리는 이 정보를 유지하기 위해 Contact data structure에 data member를 추가한다:
closing velocity처럼, 그 관통깊이는 둘 다 size and sign을 갖는다는 것을 주목해라. 음의 깊이는 두 오브젝트들이 어떠한 관통도 없다는 것을 나타낸다. 0의 depth는 두오브젝트들이 단순히 닿고있는 것을 나타낸다.
관통을 해결하기 위해, 우리는 관통 깊이를 체크한다. 만약 그것이 이미 0 이하라면, 그러면 우리는 어떠한 행동을 취할 필요가 없다; 그렇지 않다면, 우리는 그 오브젝트를 충분히 떨어지게 움직일 수 있다. 그 관통깊이가 0이 되도록. 그 관통 깊이는 contact normal의 방향으로 주어져야 한다. 만약 우리가 contact normal방향으로 penetration depth와 동일한 거리로 그 오브젝트들을 움직인다면, 그러면 그 오브젝트들은 더 이상 접촉해 있지 않을 것이다. 우리가 contact와 연관된 한 오브젝트만 가졌을 때도 같은 것이 발생한다 (즉, 그것이 game의 scenery와 관통하고 있다): 그 관통 깊이는 contact normal의 방향이다.
그래서 우리는 오브젝트가 움직여져야 하는 총 거리(즉, 깊이)와 방향을 안다. 그리고 그들은 그 방향과 깊이대로 움직일 것이다; 우리는 각 개별 오브젝트들이 어떻게 움직여져야 하는지 처리할 필요가 있다.
만약 우리가 그 접촉에 관련된 오직 한 가지 오브젝트만 가진다면, 그러면 이것은 간단하다: 그 오브젝트는 전체 거리를 움직일 필요가 있다. 만약 우리가 두 오브젝트들을 가진다면, 그러면 우리는 다양한 선택권을 가진다. 우리는 간단히 각 오브젝트를 같은 양만큼 움직일 수 있다: 관통깊이의 절반씩. 이것은 어떤 상황에서 작동할 것이지만, 신뢰성의 문제를 일으킬 수 있다. 우리가 행성의 표면에서 쉬고있는 작은 박스를 재현한다고 상상해보아라. 만약 그 박스가 그 표면을 조금 관통하고 있는게 발견된다면, 우리는 그 박스와 행성을 같은 양만큼 움직여야만 하는가?
우리는 그 관통이 첫 번째 지점에서 어떻게 오게되었는지 고려할 필요가 있고, 현실에서 같은 상황에서 무엇이 발생하는지를 고려해야 할 필요가 있다. 그림 7.3 관통하는 박스와 행성을 보여준다. 마치 실제 물리가 작동하는 것처럼. 우리는 가능한 그림 B에서 상황과 가까워지고 싶다.
이것을 하기 위해, 우리는 두 오브젝트들을 그것들의 질량에 반비례하도록 움직일 필요가 있다. 더 큰 질량을 가진 오브젝트는 거의 변화가 없고, 아주 작은 질량을 가진 한 오브젝트는 많이 움직이게 된다. 만약 그 오브젝트들 중 하나가 무한의 질량을 갖는다면, 그러면 그것은 움직이지 않을 것이다: 그 다른 오브젝트들은 전체적으로 움직여진다.
각 오브젝트의 총 움직임은 관통 깊이와 동일하다:
여기에서 delta(p_a)는 object a가 움직여져야 할 스칼라 거리이다 (우리는 나중에 방향으로 돌아올 것이다). 그 두 거리는 그것들의 질량 비율에 따라 관련되어 있다:
이 결합된 것을 우리에게 다음을 준다
이것을 contact normal의 방향과 합쳐서, 우리는 다음의 벡터 포지션의 총 변화를 얻는다
여기에서 n은 contact normal이다. (두 번째 방정식에서의 마이너스 부호를 유의해라: 이것은 contact normal이 오브젝트 a의 관점에서 주어지기 때문이다.)
우리는 그 interpenetration resolution 방정식을 이 함수로 구현할 수 있다:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | void GPED::ParticleContact::resolveInterpenetration(real duration) { // If we don't have any penetration, skip this step. if (penetration <= 0) return; // The movement of each object is based on its inverse mass, so // total that. real totalInverseMass = particle[0]->getInverseMass(); if (particle[1]) totalInverseMass += particle[1]->getInverseMass(); // If all particles have infinite mass, thenwe do nothing if (totalInverseMass <= 0) return; // FInd the amount of penetration resolution per unit of inverse mass. glm::vec3 movePerIMass = contactNormal * (penetration / totalInverseMass); // Calculate the movement amounts particleMovement[0] = movePerIMass * particle[0]->getInverseMass(); if (particle[1]) particleMovement[1] = movePerIMass * -particle[1]->getInverseMass(); else particleMovement[1] = glm::vec3(0.0); // Apply the penetration resolution particle[0]->setPosition ( particle[0]->getPosition() + particleMovement[0] ); if (particle[1]) { particle[1]->setPosition ( particle[1]->getPosition() + particleMovement[1] ); } } |
우리는 이제 충돌에서 속도 변화를 적용할 코드를 가졌고, 서로 관통하는 오브젝트들을 해결하는 코드를 가졌다. 만약 너가 contact resolution system을 구현하고 작동시킨다면, 그것은 medium-speed collisions에 대해 잘 작동할것이지만, 쉬고 있는 오브젝트들 (예를들어, 테이블에서 쉬고있는 한 파티클)은 진동하는 것처럼 보일지도 모르고, 심지어 공중으로 때때로 뛰어오를지도 모른다.
완전하고 안전한 contact resolution system을 갖기 위해서, 우리는 두오브젝트가 닿고있지만 매우 작거나 zero closing velocity를 가지고 있을 때 무엇이 발생하는지를 다시 고려할 필요가 있다.
7.2.3 Resting Contacts
그림 7.4에서 보여지는 상황을 고려해보자. 우리는 땅에서 쉬고있는 한 파티클을 가진다. 그것은 중력이라는 한 힘 만을 경험하고 있다. 첫 번째 프레임에서, 그 파티클은 아래쪽으로 가속된다. 그것의 속도는 증가하지만, 그것의 위치는 변하지 않고 남아있는다 (그것은 그 프레임의 시작에 어떠한 속도를 가지고 있지 않는다). 두 번째 프레임에서, 그 위치는 업데이트되고, 그 속도는 또 다시 증가한다. 이제,그것은 아래로 내려가고 있고, 그 땅과 서로 관통하기 시작했다. 그 충돌 탐지기는 관통을 골라내고, 충돌을 발생시킨다.
그 contact resolver는 그 파티클을 보고, 그것이 다음의 관통 velocity를 갖는 것을 본다
충돌 반응을 적용하여, 그 파티클은 다음의 속도가 주어진다:
그리고 그 파티클은 관통에서 빠져나온다. frame3에서 그러므로 그것은 위 방향의 속도를 가지게 되고, 그것은 파티클을 땅 위로 옮기고, 공중으로 튀어오르게 한다. 그 위로 향하는 속도는 작을 것이지만, 눈치챌만하기에 충분할 것이다. 특히, frame 1 또는 2가 비정상적으로 길다면, 그 속도는 크게 축적될 기회를 가질 것이고,그 파티클을 하늘로 보낼 것이다. 만약 너가 변할 수 있는 프레임율을 가진 게임에 대해 이 알고리즘을 구현하고, 그 프레임율을 늦춘다면 (window를 이리저리 끌어보아라, 예를들어, 또는 백그라운드에 이메일이 도착하게 하거나), 어떤 resting objects는 갑자기 뛰어오를 것이다.
이 문제를 해결하기 위해서, 우리는 두 가지 것을 할 수 있다. 처음에 우리는 contact를 그 이전에 탐지할 필요가 있다. 그 예제에서, 두 프레임이 우리가 문제가 있다는것을 발견하기 전에 지나갔다. 만약 우리가 가깝지만 꽤 관통하지 않는 접촉을 반환하도록 충돌 탐지기를 설정한다면, 그러면 우리는 frame 1 이후를 처리하도록 하는 contact를 얻는다.
두 번째로, 우리는 한 오브젝트가 한 프레임동안 작용하는 그것의 힘으로부터 발생하는 속도를 언제 가질지를 인지할 필요가 있다. frame 1 이후에, 그 파티클의 속도는 한 프레임 동안 그것에 작용하는 중력의 힘에 의해 단독으로 야기되어진다. 우리는 간단히 힘에 frame duration을 곱하여 오직 그 힘만이 그 오브젝트에 작용한다면 어떻게 될지를 처리할 수 있다. 만약 그 오브젝트의 실제 속도가 이 값 이하라면 (또는 그것보다 조금 위거나, 만약 우리가 반올림 문제가 만들어질 수 있다는 것을 인정한다면, ) 우리는 그 파티클이 이전 프레임에 정지해있다는 것을 안다. 이 경우에, 그 접촉은 충돌점이라기 보다는 휴식점이 될 가능성이 높다. 충돌을 위해 충격 계산을 하는 것 대신에, 우리는 zero separating velocity를 만들어낼 impulse를 적용할 수 있다.
이것은 resting contact에 대해 발생할 것이다: 어떠한 closing velocity도 축적될 시간을 가지지 않을것이다. 그래서 접촉이후의 어떠한 separating velocity도 없을것이다. 우리의 경우에, 우리는 우리가 가진속도가 우리가 시간을 프레임들로 쪼개는 방식의 부산물이 될 가능성이 높다는 것을 본다. 그리고 그러므로 우리는 그 오브젝트를 마치 contact 이전에 zero velocity를 가진 것으로 볼 수 있다. 그 파티클은 zero velocity가 주어진다. 이것은 매 프레임마다 발생한다: 실제로, 그 파티클은 항상 그림 7.4에서의 그림 1에서 처럼 남아있는다.
너는 또한 다른 방식으로 이것을 볼 수 있다. 우리는 매 프레임에서 0의 복구계수를 가진 충돌을 수행할 것이다. 이러한 일련의 micro-collisions는 그 오브젝트를 분리되게 유지한다. 이 이유 때문에, 이 방식으로 휴식점을 처리하는 한 엔진은 가끔씩 "micro-collision engine"으로 불려진다.
Velocity and Contact Normal
우리가 휴식점에 있는 두 오브젝트들을 가졌을 때, 우리는 둘 중 하나의 절대 속도 보다는 그것들의 상대 속도에 관심이 있다. 그 두 오브젝트들은 한 방향에서 서로와 함께 휴식점에 있을지도 모른다, 그러나 다른 방향으로 서로를 가로질러가면서. 한 박스는 비록 그것이 동시에 표면을 가로질러서 미끄러질지라도 땅 위에서 휴식하고 있을지도 모른다. 우리는 진동점(vibrating contact) code가 서로를 가로질러서 미끄러지는 오브젝트들의 쌍을 처리하기를 원한다. 이것은 우리가 둘 중 하나의 오브젝트의 절대값을 사용할 수 없다는 것을 의미한다.
이 상황을 처리하기 위해, 그 속도와 가속도 계산들은 모두 contact normal 방향만으로 수행된다. 우리는 처음에 이 방향의 속도를 찾고, 그것이 같은 방향의 가속도 컴포넌트에의해 단독으로 야기되었는지를 보기 위해 검증한다. 만약 그렇다면, 그 속도는 바뀐다. 그래서 이 방향으로 어떠한 separating or closing velocity가 없게 한다. 여전히 어떤 방향으로의 relative velocity가 있을지도 모르지만, 그것은 무시된다.
우리는 이 방식으로 충돌 처리 함수에 특별 케이스 코드를 추가할 수 있다.
두 오브젝트들이 resting contact에 있게 유지하여, 우리는 매프레임에서 속도에 작은 변화를주고 있다. 그 변화는 한프레임이 지나감에 따라 서로에 대해 고정하는 것으로붜 발생하는 속도의 증가를 고칠만큼 충분히 크다.
Other Approaches to Resting Contact
내가 위에서 설명했던 micro-collision approach는 많은 가능성들 중의 하나이다. Resting contact는 물리 엔진에서 옳게 하기에 힘든 두 개의 주된 도전들 중의 하나이다 (그 다른 것은 마찰이다: 사실, 그 두 개는 종종 함께 간다). 셀 수 없는 변형과 tweaks만큼의 많은 routes of attack이 있다.
나의 솔루션은 어느정도 임시방편이다. 효과적으로 우리는 한 대강의 구현에서 실수를 예측하고, 그러고나서 그 사건이 발생한 후에 그것을 고치려고 노력한다. 이것은 the flavor of hack을 가지고, 구현하기쉽고, 마찰을추가하는데 있어서 적절함에도 불구하고(우리가 chapter 15에 할), engineering purists의 입장에서는 눈살이 찌푸려지는 것이다.
좀 더 물리적으로 현실적인접근법은 현실에서 한 힘이 땅으로부터 파티클에 적용되는 것을 인지하는 것이다. 이 reaction force(반작용 힘)는 그 오브젝트를 뒤로 밀게 하여, 그것의 수직 방향의 가속도가 0이 되도록 한다. 파티클이 아무리 아래로 세게 눌러질지라도, 그 땅은 같은힘으로 위로 들어올릴 것이다. 우리는 이 방식으로 작동하는 force generator를 만들 수 있다. 그리고 이것은 땅에서 어떠한 가속도도 만들 수 없도록 한다.
이것은 오직 땅에서 한 contact만을 가질 수 있는 파티클의 경우에는 작동을 잘한다. 좀 더 복잡한 rigid bodies에 대해, 그 상황은 상당이 좀 더 복잡해진다. 우리는 한 오브젝트와 땅 사이의 몇 가지 접촉 점들을 가질지도 모른다(또는 더 악화되어, 우리는 한 오브젝트와 움직일 수 없는 resting points 사이의 전체의 일련의 contacts를 가질지도 모른다). 그것은 그 오브젝트의 전체 움직임이 올바르게 되기 위해 즉시 어떻게 각 contact에서 reaction force를 계산할지가 명백하지 않다. 우리는 chapter 15에서 깊이 있게 reaction forces에 대해 돌아갈 것이고, chapter 18에서 좀 더 복잡한 resolution methods에 돌아갈 것이다.
7.3 The Contact Resolver Algorithm
그 collision resolver는 collision detection system으로부터 contacts의 리스트를 받고, contacts를 고려하기 위해 재현될 오브젝트들을 업데이트 할 필요가 있다. 우리는 이 업데이트를 수행할 세 가지 코드를 갖는다:
- 오브젝트들의 분리되어 튕겨지는것을 재현하기 위해, 오브젝트들에 충격을 적용하는 collision resolution function.
- 오브젝트들이 부분적으로 서로 삽입되지 않도록 하기 위해, 오브젝트들을 떨어지도록 움직이게 하는 interpenetration resolution function.
- collision resolution function안에 있고, 충돌이기 보다는 resting일지도 모르는 contacts에 대해 주목하지 않도록 하는 resting contact code.
한 contact가 그것의 separating velocity와 interpenetration depth에의존하는것을요구하는 이러한 함수들의 필요성이다. Interpenetration resolution은 만약그contact가 0보다 더 큰 관통 깊이를 가질 때 만 오직 발생할 필요가 있다. 유사하게, 우리는 collision resolution이 없을 때에만 interpenetration resolution만을 할 필요가 있다. 만약 그 오브젝트들이 관통되어있지만 떨어지고 있다면.
필요한 함수들의 조합에 상관없이, 각 contact는 한 번에 하나씩 해결된다. 이것은 실제 세계를 간단하게 한 것이다. 현실에서, 각 contact는 다소 다른 time의 instant로 발생하거나 다양한 시간에 대해서 차지된다. 몇 가지 contacts는 그것들의 효과를 연속으로 적용한다; 다른 것들은 그것들이 영향을 미치는 오브젝트에 동시에 합치거나 작용한다. 몇 가지 물리엔진은 이것을 정확히 따라할려고 노력할것이다. 그리고 순차적인 접촉들을 그것들의 정확한 순서로 다루고, restingcontacts를 모두 동시에 해결 할것이다. Section 7.3.2에서, 우리는 순차적인 연속을 명예롭게 하는 대안의 resolution scheme을 볼 것이다. Chapter 18에서, 우리는 다양한 contacts의 동시 resolution을 수행하는 시스템들을 볼 것이다.
우리의 엔진에 대해, 우리는 상황을 간단하게 유지하고, 둘 다 하지 않고 싶다. 우리는 모든 contacts들을 한 프레임의 끝에서 한 번에 하나씩 해결하고 싶다. 우리는 여전히 이 전략으로 매우 믿을만한 결과를 얻을 수 있다. 상당히덜 복잡하고,에러가 없는 구현과 함께. 그러나 최상의 결과를 얻기 위해서, 우리는 그 contacts들이 올바른 순서로 해결되도록 할 필요가 있다.
7.3.1 Resolution Order
만약 한 오브젝트가 두 개의 동시에 발생한 contacts를 그림 7.5에서 보여지듯이 가진다면,그러면 한 contact를 해겨랗기 위해 그것의 velocity를 바꾸는 것은 다른 contact에서의 separating velocity를 바꿀지도 모른다. 그림에서, 만약 우리가 첫 번째 contact를 해결한다면, 그러면 그 두 번째 contact는 충돌중인 것을 멈춘다: 그것은 이제 분리되고 있다. 만약 우리가 두 번째 contact만을 해결한다면, 그러나, 그 첫 번째 contact는 여전히 해결될 필요가 있다: 속도에서의 변화는 그것을 구하기에 충분하지 않다.
이것과 같은 상황에서 불필요한 작업을 하는 것을 피하기 위해, 우리는 가장 심각한 contact를 먼저해결한다: 가장 낮은 separating velocity를 가진 (즉, 가장 음수인) contact를 말한다. 편할 뿐만 아니라, 이것은 또한 물리적으로 우리가 할 수 있는 현실적이다. 그림에서, 만약 우리가 full three-object situation의 행동과 두 개의 더 낮은 blocks중에서 하나를 제거했다면 가질 행동을 비교한다면, 우리는 최종 결과가 우리가 block B가 아닌 A를 가진 경우와 가장 비슷할 거라는 것을 알 것이다. 다시 말해서, 가장 심각한 충돌들은 시뮬레이션의 행동을 지배하는 경향이 있다는 것이다. 만약 우리가 어떤 충돌들을 다뤄야 할지를 우선순위화 한다면, 그것은 가장 현실성을 주는 거들이 되어야 한다.
그림 7.5는 우리의 contact resolution algorithm의 복잡성을 보여준다. 만약 우리가 한 충돌을 처리한다면, 그러면 우리는 다른 접촉에 대한 separating velocity를 바꿀지도 모른다. 우리는 그것들의 separating velocity로 분류할 수 없고, 그것들을 그 순서로 다룰 수 없다. 우리가 첫 번째 충돌들을 다루기만 한다면, 그 다음 접촉은 양의 separating velocity를갖고 어떤 처리도 필요하지 않을 것이다.
또한 또 다른,좀 더 미묘한 문제가 있는데, 그것은 많은 파티클 상황에서 발생하지 않는 경향이 있는 것이다. 우리는 우리가 한 contact를 해결하고, 그러고나서 다른 것을 해결하지만, 그 두 번째의 해결이 첫 번째 contact가 다시 충돌이 하도록 하는 상황을 가질 수 있다. 그래서 우리는 그것을 re-resolve 할 필요가 있다. 운 좋게도, 어떤 시뮬레이션의 유형에 대해서 (특히, 마찰이 없는 것들, 비록 몇 가지 마찰 상황들이 또한 작동할 수 있을지라도), 이 반복은 결국에는 정확한 답으로 해결이 될 것이다. 우리는 영원히 반복할 필요가 없을 것이고, 우리는 전체 simulation이 폭발할 때 까지 그 corrections가 더 커지는 상황으로 끝나지 않을 것이다. 불행하게도, 이것에 도달하기에 긴 시간이 걸릴 수 있고, 그것이 얼마나 걸릴지에 대한 정확한 방법은 없다. 막히는것을 피하기 위해, 우리는 매 프레임에서 수행될 수 있는 resolutions의 수에 제한을 둔다.
우리가사용할 그 contact resolver는 이 알고리즘을 따른다:
- 각 contact의 separating velocity를 계산하고, 가장 낮은 (즉, 가장 음의) 값이 contact를 추적해라.
- 만약 그 가장 낮은 separating velocity가 0 이상이라면, 그러면 우리는 끝났다: 알고리즘을 종료시켜라.
- 가장 낮은 separating velocity를 가진 contact에 대해 collision response algorithm을 처리해라.
- 만약 우리가 좀 더 많은 반복을 가지고 있다면,그러면 step 1으로 돌아와라.
그 알고리즘은 자동으로, 그것이 이전에 해결한 contacts를 다시 검사한다. 그리고 그것은 분리되고 있는 contacts들은 무시할것이다. 그것은가장 심각한 collisions를 매 반복마다 해결한다. 반복의 수는 적어도 contacts의 개수여야하고(적어도 한 번에 보여지는 것의 기회를 그것들에게 주기 위해서) 그것보다 더 클 수 있다. 몇 가지 파티클 시뮬레이션에 대해, contacts가 있는 것 만큼의 같은 반복 개수를 갖는 것은종종 잘 작동할 수 있다. 나는 경험 상 contacts의 개수의 두 배를 사용하곤 하지만,더 많은 것은 복잡하고 상호 연결된 contacts에 대해 필요하다. 너는 또한 그 알고리즘에게 어떠한 iteration limit도 주지 않을 수 있고, 그것이 어떻게 수행하는지를 볼 수 있다. 이것은 어려운 상황이 발생할 때 디버깅에 좋은 접근법이다,
너는 내가 지금까지 interpenetration을 무시했다는 것을 눈치챘을지도 모른다. 우리는 interpenetration resolution을 collision resolution과 합칠 수 있다. 실제로 더 좋은 솔루션은 두 개의 다른 phases로 분리하는것이다. 처음에 그 이전의 알고리즘을 사용하여 차례대로 collisions를 해결한다. 둘 째로, 우리는 interpenetrations를 해결한다.
두 resolution 단계를분리하는 것은 우리가 속도에 대해 보다는 interpenetration을 해결하는 것에 대해 다른순서를 사용하는 것을 허용한다. 한 번 다시, 우리는 가장 현실적인 결과를 얻기를 원한다. 우리는 이전처럼 심각성의 순서로 contacts를 해결하여 이것을 할 수 있다. 만약 우리가 그 두 단계를 합친다면, 우리는 resolution의 하나 또는 다른 종류에 대한 suboptimal order에대해 종속된다.
그 interpenetration resolution은 collision resolution과 같은 알고리즘을 따른다. 이전처럼, 우리는 각 반복 사이에 모든 interpenetration depths를 재 계산할 필요가 있다. interpenetration depths가 collision detector에 의해 제공된다는 것을 상기해라. 우리는 매 반복 후에 collision detection을 다시 수행하는 것을 원하지 않는다. 그것이 너무 시간을 소비하기 때문이다. interpenetration depth를 업데이트 하기 위해, 우리는 이전 iteration에서두 오브젝트들을 얼마나 움직였는지를 추적한다. 각 contact에서 오브젝트들은 그러고나서 검사된다. 만약 그 오브젝트 둘 중 하나가 지난 프레임에서 움직여졌다면, 그러고나서 그것의 interpenetration depth는 contact normal의 방향에서의 움직인 component를 발견하여 업ㅂ데이트 된다.
이 모든 것을 합하여, 우리는 contact resolver function을 얻는다:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | void GPED::ParticleContactResolver::resolveContacts(ParticleContact * contactArray, unsigned numContacts, real duration) { unsigned i; iterationUsed = 0; while (iterationUsed < iterations) { // Find the contact with the largest closing velocity; real max = REAL_MAX; unsigned maxIndex = numContacts; for (i = 0; i < numContacts; ++i) { real sepVel = contactArray[i].calculateSeparatingVelocity(); if (sepVel < max && (sepVel < 0 || contactArray[i].penetration > 0)) { max = sepVel; maxIndex = i; } } // Do we have anything worth resolving? if (maxIndex == numContacts) break; // Resolve this contact contactArray[maxIndex].resolve(duration); //Update the interpenetrations for all particles glm::vec3* move = contactArray[maxIndex].particleMovement; for (i = 0; i < numContacts; ++i) { if (contactArray[i].particle[0] == contactArray[maxIndex].particle[0]) { contactArray[i].penetration -= glm::dot(move[0], contactArray[i].contactNormal); } else if (contactArray[i].particle[0] == contactArray[maxIndex].particle[1]) { contactArray[i].penetration -= glm::dot(move[1], contactArray[i].contactNormal); } if (contactArray[i].particle[1]) { if (contactArray[i].particle[1] == contactArray[maxIndex].particle[0]) { contactArray[i].penetration += glm::dot(move[0], contactArray[i].contactNormal); } else if (contactArray[i].particle[1] == contactArray[maxIndex].particle[1]) { contactArray[i].penetration += glm::dot(move[1], contactArray[i].contactNormal); } } } ++iterationUsed; } } |
7.3.2 Time-Division Engines
interpenetration을 해결하거나 contacts에대해 민감한 resolution order를 생성해야 하는것을 피하는 물리엔진을 만드는 것에 대한 또 다른 접근법이 있다. 프레임당 물리엔진의 단일의 업데이트를 갖기보다는,우리는 충돌에 의해 간간히 끼어들어오는 많은 업데이트들을 가질 수 있다.
그 이론은 이것처럼 진행된다:
- 어떠한 충돌도 없을 때, 오브젝트들은 자유롭게 움직인다. 그리고 이것은 지난챕터에서 우리가 보았던 운동 법치과 force generators를 사용한다.
- 충돌이 발생할 때, 그것은 두 오브젝트들이 닿는 정확한 지점에 있는다. 이 단계에서 어떠한 관통은 없다.
- 만약 우리가 정확히 한 충돌이 언제 발생할지를 탐지할 수 있다면, 우리는 이 시점까지 보통의 운동법칙을 사용할 수 있고, 그러고나서 멈추어서, impulse calculations을수행할 수 있고, 그러고나서 다시 보통의 운동 법칙으로 시작할 수 있다.
- 만약 수 많은충돌들이 있다면, 우리는 그것들을 순서대로 처리한다; 각 충돌 사이에 우리는 보통의 운동법칙을 사용하여 world를 업데이트 한다.
실제로, 이러한 종류의 엔진은 이 알고리즘을 갖는다:
- 시작 시간이 현재 simulation time이 되게하고, 끝나는 시간이 현재 update request의 마지막이 되도록해라.
- 전체 time interval에 대해 완전한 업데이트를 수행해라.
- collision detector를 작동시키고, 충돌들의 리스트를 모아라.
- 만약 어떠한 충돌도 없다면, 우리는 끝났다 : 알고리즘을 종료해라.
- 각 충돌에 대해, 첫 번째 충돌의 정확한 시간을 처리해라.
- 일어날 첫 번째 충돌을 선택해라.
- 만약 첫 번째 충돌이 end time후에 발생한다면, 그러면 우리는 끝났다 : 알고리즘을 종료해라.
- step 2로부터 완전한 업데이트를 제거하고, start time부터 첫 번째 충돌 시간까지의업데이트를 수행해라.
- 충돌을 처리하고, 적절한 충격을 적용하고(어떠한 interpenetration resolution이 필요하지 않다, 왜냐하면 충돌의 순간에 오브젝트들은 닿고만 있을 뿐이다).
- 시작 시간을 첫 번째 충돌 시간으로 설정하고, 그 마지막 시간을 바꾸지 말고, step 1으로 돌아가라.
이것은 정확한 결과를 주고, interpenetration resolution이 가진 문제를 피한다. 그것은 흔히 공학 물리 프로그램에서 사용되는 알고리즘인데, 그 프로그램에서 정확성은 가장 중요하다. 불행히도, 그것은 매우 시간을 잡아먹는다.
각 충돌에 대해, 우리는 collision detector를 다시 작동시키고,매번 regular physics update를 다시 작동시킨다. 우리는여전히 resting contacts를 처리할 특별 케이스 코드를 가질 필요가 있다; 만약 그렇지 않다면, 그 resting contacts는 매 반복에서 첫 번째 충돌로서 돌아갈 것이다. 심지어 resting contacts없이, collision detection calculations에서의 numerical errors들이 끝나지 않는 cycle을 야기할 수 있다 - 동시에 충돌이 발생하는 끊임없는 흐름을 말한다, 이것은 그 알고리즘이 끊임없이 반복하도록 야기시킨다.
거의 모든 프로젝트에 대해, 이 접근법을 실용적이지 않다. 프레임마다 한 번 업데이트 시키는 방법(once-per-frame update))이 더 좋은 솔루션이다. 그리고 거기에서, 모든 contacts들은 속도와 interpenetration에 대해 해결된다.
내가 생각하는 "almost"의 경우는 pool, snooker, or billiards games이다. 이러한 경우에, 충돌할 때 충돌의 순서와 공의 위치가 매우 중요하다. 두 개의 공이 충돌할 때 once-per-frame physics를 사용하는 pool game은 신뢰할만할지도 모른다. 그러나, 그 cue ball이 꽉 쌓아진 (아직 닿고있지 않는) 많은 공들과 부딪힐 때, 이상한 효과들이 나타날 수 있다. 진지한 시뮬레이션을위해서, 이전의 알고리즘을 따르는 것이 거의 필수적이다. 만약 너가 처음부터 작성한다면, interpenetration code없이 구현하는 것이 더 쉽다는 장점과 함께(모든 공이 같은 질량을 갖기 때문에,너가 얻을수 있는 간단함은 말할 필요도 없이).
너는 더 오래된 PC들에서 작동하는 pool simulation games에서 이것을 볼 수 있다. 너가 멈출때, cueball 이 그 pack을 칠 때 두 번째 중지의 순간이 있다. 수천개의 내부 충돌들이 탐지되고 순차적으로 처리되기 때문이다.
단일의 arcade pool game에대해, 만약 너가 이미 once-per-frame physics engine이 이용가능하게 한다면, 시도할 가치가있다: 그것은 그 일을 하는데 충분히 좋다.
7.4 CollisionLike Things
스프링에서 처럼, 우리는 이 챕터에서의 기법들을 사용하여 모델링 될 수 있는 몇 가지 유형의 연결들을 볼 것이다.
너는 두 오브젝트들을 적어도 어떤 최소 거리가 떨어지도록 유지하기 위해 작용하는 것을 충돌로 생각할 수 있다. 한 contact는 만약 그것들이 너무 가까워지면 두 오브젝트 사이에서 생성된다. 같은 이유로, 우리는 오브젝트들을 함께 유지하는데 contacts를 사용할 수 있다.
7.4.1 Cables
한 cable은 두오브젝트들이 그것의 길이 이상으로 분리되지 않게끔 강요하는 제약이다. 만약 우리가 가벼운 케이블로 연결된 두 오브젝트들을 가진다면, 그것들이 가까이 있는 한, 그것들은 어떠한 효과도 느끼지못할 것이다. 그 케이블이 팽팽하게 당겨졌을 때, 그 오브젝트들은 더 멀리 분리되어질 수 없다. 케이블의 특징에 의존하여, 그 오브젝트들은 이 한계를튕겨나가는 것처럼 보일지도 모른다. 부딛히는 오브젝트들이튕겨서 떨어져 나가는 방식대로. 그 케이블은 이 bounce effect를 통제하는 특징적인 복구 계수를 가진다.
우리는 그 케이블의 끝들이 너무 멀리 분리될 때 마다, contacts를 생성하여 케이블을모델링할수 있다. 그 contact는 충돌을 위해 사용되는 것과 많이 닮았다. 그것의 contact normal이 반대인 것을 제외하고: 그것은 오브젝트들을 튕겨나가게 하기 보다, 당긴다. 그 접촉의 관통 깊이는 그 케이블이 그것의 한계를 넘어서 얼마나 펼쳐졌는가와 대응된다.
우리는 이 방식으로 cable에 대한 contact generator를 구현할 수 있다:
이 코드는 collision detector 처럼 작동한다: 그것은 케이블의 현재 상태를 검사하고, 만약 그 케이블이 그것의 limit에 도달하면 한 contact를 반환한다. 이 contact는그러고나서 충돌 탐지기에 의해 생성된 모든 다른 것들에 추가되어져야하고, normal contact resolver algorithm에서 처리되어져야 한다.
7.4.2 Rods
Rods는 케이블과 충돌의 행동들을 합친다. rod에 의해 연결된 두 오브젝트들은 분리되지도 더 가까워지지도 않는다. 그것들은 고정된 거리에서 떨어져서 유지된다.
우리는이것을 cable contact generator에서 했던 것과 같은 방식으로 이것을 구현할 수 있다. 각 프레임에서,우리는 그 rod의 현재 상태를 보고, 그 끝을 안으로 가져올지 또는 그것들을 불리할지 둘 중 하나의 contact를생성한다.
그러나, 우리는 우리가 이제까지 본 것 중에서 두 가지 수정을 할 필요가 있다. 첫 째로, 우리는 항상 0의 복구 계수를 사용해야만 한다. 그것은 함께 튕기거나 분리되는 양 끝단에 있는 것들에게는 말이 되지 않는다. 그것들은 서로로부터 같은 거리에서 유지되어야 한다. 그래서 그것들 사이의 line을 따라 그것들의 상대 속도는 0이 되어야만 한다.
둘 째로, 만약 우리가 그 두 contacts들 중에 하나를 적용한다면 (분리하거나 가깝게 하도록) 매 프레임마다, 우리는 진동하는 rod를 갖게 될 것이다. 연속적인 프레임들에 대해, 그 rod는 너무 짧을 가능성이 있고, 너무 길수도 있다. 그래서 각 contact는 그것을 앞뒤로 끌어와야 할 것이다. 이것을 피하기 위해, 우리는 매 프레임에서 두 contats를 생성한다. 만약 그 contacts들 중의 하나가 필요하지 않다면(즉, 분리되는속도가 0보다 크다, 또는 어떠한 관통이 없다), 그러면 그것은 무시되어질 것이다. 추가 contact를 갖는 것은 거기에서 contact resolver algorithm이 과도하게 보상되는 것을 방지하는데 도움이 된다. 그래서 그 rod는 좀 더 안정하게 될 것이다. 이 접근법의 단점은 rods의 복잡한 모음에 대해, 정말 안전한 솔루션을 도달하는데 필요한 반복회수가 극적으로 상승할 수 있다. 만약 너가 낮은 iteration limit을 가진다면, 그 vibration은 돌아올 수 있다.
우리는 우리의 contact generator를 이렇게 구현할 수 있다:
그 코드는 항상 두 개의 contacts를 생성한다.그리고 이것은 충돌 탐지기에 의해 반환된 리스트에 추가되어야 한다. 그리고 contact resolver에 넘겨져야 한다.
7.5 Summary
우리는 이제 rods와 cables같은 hard constraints와 springs와 bungees같은 elastic constraints 둘 다를 사용하여 함께 파티클들을 연결할 수 있는 물리 코드의 한 집합을 구성했다.
Rods와 cables은 분리된 오브젝트 사이의 충돌과 유사하게 행동한다. 케이블들은 함께 구성된 파티클들이 서로를 향해서 튕기도록 야기시킬 수 있다. 파티클들이 충돌할 때 서로 튕겨나가는 것처럼. 같은 방식으로 rods가 연결된 파티클들이 함께 유지되도록 한다. 그리고 고정된 분리 거리로 움직이도록 한다. 이것은 bounce가 없는 충돌과 동일하다 - 파티클들이 서로 붙어 있고, 그것들의 가까워지는 속도가 0으로 될 때이다.
파티클들 사이의 두 hard and elastic connections를 지원하는 것은 우리가 흥미로운 구조를 만들고, 그것들을 게임에서 재현하는 것을 허용하게 한다.
이것은 우리의 2차의 완전한 물리엔진을 형성한다 : mass-aggregate engine. 우리가 처음에 구성한 파티클 엔진과 다르게, 그 mass-aggregate engine은 출팔된 게임들에서 희귀하다. 그것은 많은 2차원 플랫폼 게임들에서 좋은 효과를 위해 사용되어 왔다. 그것이 이 책의 나중에 설명되는 좀 더 복잡한 엔진으로 크게 대체되었을지라도, 그것은 몇 몇 게임에서 그것 자신의 방식으로 여전히 유용하다. Chapter 8은 그것의 장점과 몇 가지 적용들을 본다.
댓글 없음:
댓글 쓰기