5. Adding General Forces
part 1에서, 우리는 중력의 힘을 포함한 파티클 물리엔진을 만들었다. 우리는 chapter 3에서 힘에 관한 수학을 보았다. 그리고 이것은 최종 가속도를 계산하여 우리가 원하는 어떤 힘을 재현하게 해준다.
이 챕터에서, 우리는 우리의 물리엔진을 확장하여, 그것이 동시에 작용하는 많은 다른 힘들을 처리할 수 있도록 한다. 우리는 중력이 하나의 힘이라고 가정할 것이다. 비록 이것은 제거되거나 또는 요구된다면 0으로 설정될 수 있을 지라도. 우리는 또한 force generators를 볼 것이다: 게임 세계의 현재 상태를 기반으로하는 힘들을 계산할 수 있는 코드이다.
5.1 D'Alembert's Principle
비록 우리가 한 힘이 오브젝트에 작용할 때, 그것의 행동에 관한 방정식을 가질지라도, 우리는 하나 이상의 힘이 작용할 때 무엇이 발생할지를 고려하지 않았다. 명백히 그 행동은 힘이 홀로 작용하는 것과는 다를 것이다: 하나의 힘은 다른 것에 반대 방향으로 작용하거나, 그것을 평행하게 강화시킨다. 우리는 모든 힘들의 결과로서 전체 행동을 처리하는 메커니즘이 필요하다.
D'Alembert(달랑베르)의 원리는 여기에서 구조대가 된다. 그 원칙은 그 자체로 좀 더 복잡하고, 우리가 여기에서 고려할 필요가 있는 것 보다 더 멀리 있는 것이다. 그것은 움직임의 방정식의 다른 공식들의 양을 연관짓는다. 그러나 그것은 우리가 이 책에서 이용할 두 가지 중요한 암시를 갖는다. 그 첫 번째는 여기에서 적용된다; 그 두 번째는 chapter 10에서 발생할 것이다.
파티클들에 대해, D'Alembert의 원칙은 만약 우리가 한 오브젝트에 작용하는 힘들의 한 집합을 가진다면, 우리는 모든 그러한 힘을 단일의 힘으로 대체할 수 있다. 그리고 이것은 다음으로 계산된다
다시 말해서, 우리는 벡터 덧셈을 이용하여 그 힘들을 함께 더하고, 우리는 발생하는 그 단일의 힘을 적용한다.
이 결과를 이용하기 위해, 우리는 force accumulator로서 벡터를 사용한다. 각 프레임마다, 우리는 그 벡터를 0으로 만들고, 벡터 덧셈을 이용하여 차례로 각 적용된 힘을 더한다. 최종 값은 그 오브젝트에 적용할 합력(resultant force)이다. 우리는 적용될 힘의 accumulator를 clear하기 위해 각 적분 단계의 끝에서 호출되는 파티클에 대한 메소드를 더한다:
이 accumulation stage(축적 단게)는 파티클이 적분되기전에 완료될 필요가 있다. 적용될 모든 힘은 그들 스스로를 accumulator에 스스로 더 할 기회를 가질 필요가 있다.
우리는 수동으로 우리의 frame update loop에 적절한 힘을 더하는 코드를 추가하여 이것을 할 수 있다. 이것은 몇 프레임 동안만 발생할 힘들에 적절하다. 대부분의 힘들은 확장된 기간동안 한 오브젝트에 적용될 것이다.
우리는 registry를 만들어서 이러한 long-term forces를 관리하는 것을 더 쉽게 할 수 있다. 한 힘은 파티클과 함께 그 자체를 등록하고, 그러고나서 각 프레임마다 한 힘이 제공할지를 요청되어질 것이다. 나는 이러한 것을 "force generators"라고 부를 것이다.
5.2 Force Generators
우리는 한 오브젝트에 다양한 힘을 넣을 메커니즘을 가졌다. 우리는 이제 이러한 힘들이 어디서 오는지를 처리할 필요가 있다. 중력의 힘은 꽤 직과넞ㄱ이다: 그것은 항상 게임에서 ㅁ든 오브젝트에게 존재한다.
몇 가지 힘들은 한 오브젝트의 행동 때문에 발생한다 - 예를들어, 세심한 drag force. 다른 힘들은 한 오브젝트가 그 자체가 발견하게 되는 환경의 결과이다: 흘러다니는 물체에 대한 부력과 폭발로부터 오는 폭발력이 예시들이다. 그러나 힘의 다른 유형들은 오브젝트가 함께 연결되어있는 방식의 한 결과이다; 우리는 다음 챕터에서 스프링처럼 작동하는 힘들을 볼 것이다. 마지막으로 플레이어 (또는 AI-controlled 캐릭터)가 그것들을 요구하기 때문에 존재하는 힘들이 있다: 예를들어 차에 있는 가속도 또는 jetpack으로부터의 추력.
다른 복잡함은 몇 가지 힘의 dynamic nature이다. 중력의 힘은 쉽다. 그것이 항상 상수기 때문이다. 우리는 그것을 한 번 계산할 수 있고, 그 게임의 나머지에 고정된 채로 둔다. 대부분의 다른 힘들은 끊임없이 변화한다. 몇 가지는 한 오브젝트의 위치 또는 속도의 결과로서 변화한다: drag(저항) 더 높은 스피드에서 더 강하고, 스프링의 힘은 그것이 더 압축될수록 더 커진다. 다른 것들은 외부 요소 때문에 변화한다: 그가 thrust button을 release할 때, 폭발 dissipates, 또는 player의 jetpack burst는 갑자기 끝날 것이다.
우리는 그것들의 계산에 대해 다른 메카닉을 가진 다양한 다른 힘들을 다룰 수 있을 필요가 있다. 몇 가지는 것을 상수일지도 모르고, 다른 것들은 그 오브젝트의 현재 특성 (위치나 속도 같은)들을 함수에 적용할지도 모른다. 몇몇은 사용자 입력을 요구할 지도모르고, 어떤 것들은 시간을 기반으로 할지도 모른다.
만약 우리가 간단히 모든 이러한 유형의 힘들을 물리엔진 프로그래밍 하고, 각 오브젝트에 대해 그것들을 섞고 match시킬 파라미터들을 설정한다면, 그 코드는 급격히 관리할 수 없을 것이다. 이상적으로 우리는 한 힘이 계산되는 방식의 세부사항을 추상화할 수 이씩를 원하고, 그 물리엔진이 일반적으로 힘들과 간단하게 작동하도록 허용하길 원한다. 이것은 우리가 한 오브젝트에 어떤 개수의 힘이든 적용시키게 허용할 것이다. 그 오브젝트가 이러한 힘이 어떻게 계산되었는지의 세부사항을 알 것 없이.
나는 이것을 "force generator"라고 불려지는 한 구조를 통해서 이것을 할 것이다. 힘의 종류들이 있는 것 만큼 많은 다른 force generator의 종류들이 있을 수 있지만, 각 오브젝트는 한 generator가 어떻게 작동하는지를 알 필요가 없다. 그 오브젝트는 간단히 각 generator와 연관된 힘을 찾기 위해 일관된 interface를 사용한다: 이러한 힘들은 적분 단계에 축적되어질 수 있고 적용될 수 있다. 이것은 우리가 오브젝ㅌ으ㅔ 어떤 갯수의 힘이든 적용할 수 있게 해준다, 우리가 어떤 유형을 선택하든. 그것은 우리가 필요할 때 마다, 물리엔진에서 어떤 코드를 다시 쓸 필요 없이 새로운 게임 or levels에 대해 새로운 유형의 힘을 만들는 것을 허용한다.
모든 물리엔진이 force generators의 개념을 가진 것은 아니다: 많은 것들이 힘을 추가하는 손으로 쓰여진 코드를 요구하거나 가능한 힘들을 소량의 공통된 옵션으로 제한한다. general solution을 갖는 것은 좀 더 유연하고 우리가 좀 더 빠르게 실험하게 해준다.
이것을 구현하기 위해, 우리는 "interface"라고 불려지는 객체지향설계 패턴을 사용할 것이다. Java와 같은 몇 가지 언어들은 이것을 언어의 일부로서 내장해놓았다; 다른 것들에서 그것은 일반 클래스로 근사되어질 수 있다. 우리가 force generator code를 보기전에, 나는 간단히 interface의 개념 그리고 그것의 친척인 polymorphism의 개념을 리뷰할 것이다.
5.2.1 Interfaces and Polymorphism
프로그래밍에서, interface는 한 소프트웨어 컴포넌트가 다른 것들과 어떻게 상호작용하는지에 대한 명세이다. 객체지향 언어에서, 그것은(interface) 일반적으로 클래스를 말한다: 한 인터페이스는 한 클래스가 노출할 메소드, 상수, 데이터 타입, 그리고 예외(즉, 에러) 에 대한 명세이다. 그 인터페이스는 그 자체로 클래스는 아니다; 그것은 클래스의 어떤 개수가 이행할 명세이다. 한 클래스가 명세를 이행할 때, 그것은 인터페이스를 구현한다고 말해진다 (사실, Java는 인터페이스를 구현하는 한 클래스를 표기하기 위해 explicit한 implements keyword를 사용한다).
인터페이스에 대해 강력한 것은 그것들의 다형성의 사용이다. Polymorphism은 그것이 몇 가지 명세를 이행하는 것을 기반으로 몇 가지 소프트웨어 컴포넌트를 사용하는 한 언어의 능력이다. 우리의 목적을 위해서 그 컴포넌트들은 클래스이고, 그 명세는 인터페이스다. 만약 우리가 그 시스템의 다른 부분을 사용할 필요가 있는 어떤 코드를 쓰려고 한다면, 우리는 상호작용을 위한 인터페이스를 정의할 수 있고, 호출하는 코드가 그 인터페이스에 있는 요소만을 쓰도록 할 수 있다. 우리는 나중에 코드가 상호작용하고 있는 것을 바꿀 수 있고, 그것이 같은 인터페이스를 구현하는한, 그 호출하는 코드는 결코 그 차이를 알지 못할 것이다.
이 교체성(replaceability)는 우리의 목적에 중요한 것이다: 우리는 force generator에 대한 인터페이스를 가지고, polymorphism을 통해, 우리는 그 force generator가 무슨 종류의 힘을 나타내는지를 알 필요가 없다. 그 force generator가 그 인터페이스를 구현하는한, 우리는 관련된 정보를 추출할 필요가 있다. 이것은 다른 부분이 구현되는 방식에 매우 의존하는 프로그램의 다른 부분을 갖는 것을 피하도록 하는 도움이 되는 방식이다: 우리는 인터페이스를 만들고, 한 클래스가 그 인터페이스를 구현하는 한, 그 호출하는 코드는 그것에 대해서 더 이상 알 필요가 없다.
C++에서, 언어에서 어떤 전용 interface structure가 없다. 대신에 우리는 pure virtual functions의 선택으로 base class를 사용한다. 이것은 우리가 base class의 instance를 만들 수 없도록 보장한다. base class로부터 파생된 각 클래스는 그러고나서 그것이 인스턴스화 되기전에 그것의 모든 메소드들을 구현해야 한다.
5.2.2 Implementation
force generator interface는 오직 현재의 힘만을 제공할 필요가 있다. 이것은 그러고나서 축적되어질 수 있고, 그 오브젝트에 적용될 수 있다. 우리가 사용할 인터페이스는 이것처럼 보일 것이다:
class ParticleForceGenerator { public: /** * Overload this in implementations of the interface to calculate * and update the force applied to the given particle. */ virtual void updateForce(GPEDParticle* particle, real duration) = 0; };
그 updateForce method는 frame의 duration동안 넘겨진다. 그리고 그 duration 동안, 그 힘은 필요하고, 힘을 요구하는 파티클에 대한 포인터도 필요하다. 그 프레임의 duration은 몇 몇 force generators들을 위해 필요하다 (우리는 chapter 6에서, 아슬아슬하게 이 값에 의존하는 spring-force generator를 만날 것이다).
우리는 force generator가 그 오브젝트 그 자체를 추적 할 필요가 없기 때문에 그 오브젝트의 포인터를 함수에 넘긴다. 이것은 또한 우리가 동시에 몇 가지 오브젝트들에 붙여질 수 있는 force generators를 만들게 해준다. generator instance는 특정한 오브젝트에 툭수한 어떤 데이터도 포함하지 않는한, 그것은 간단히 힘을 계산하기 위해 넘겨진 오브젝트를 사용할 수 있다. section 5.2.3과 5.2.4에서 묘사되는 예제 force generators 둘 다 이 특성을 갖는다.
그 force generator는 어떠한 값도 반환하지 않는다. 우리는 그것이 force accumulator에 더 할 힘을 반환하게 할 수 있지만, force generators는 몇 가지 힘을 반환해야 할 것이다 (비록 그것이 0일지라도), 그리고 그것은 전체적으로 다른 종류의 힘을 지원하려 할 때 우리가 그 책의 나중에 사용할 유연성을 제거할 것이다. 대신에, 만약 한 force generator가 한 힘을 적용하길 원한다면, 그것은 넘겨진 오브젝트에 addForce method를 호출할 수 있다.
force generators의 interface 뿐만 아니라, 우리는 어떤 force generators가 어떤 particles들에 영향을 미칠지를 등록할 수 있을 필요가 있다. 우리는 linked list 같은 자료구조 또는 generators의 성장가능한 배열로 이것에 각 파티클을 더할 수 있다. 이것은 유효한 접근법이지만, 그것은 성능에 대한 의미를 갖는다: 각 파티클이 많이 낭비되는 저장 메모리를 갖을 필요가 있거나 (growable array를 사용하여) 또는 새로운 등록은 많은 메모리 연산을 야기시킬 것이다 (링크드 리스트에서 원소를 만드는 것). 성능과 modularity를 위해, 나는 그 디자인을 분해하고 파티클과 force generators의 중앙 registry를 갖는 것이 더 좋다고 생각한다. 내가 제공하는 것은 이것처럼 생긴다:
/** * Holds all the force generators and the particles they apply to. */ class ParticleForceRegistry { protected: /** * Keeps track of one force generator and the particle it * applies to */ struct ParticleForceRegistration { GPEDParticle* particle; ParticleForceGenerator* fg; }; /** * Holds the list of registrations. */ typedef std::vector<ParticleForceRegistration> Registry; Registry registrations; public: /** * Registers the given force generator to apply to the * given particle */ void add(GPEDParticle* particle, ParticleForceGenerator* fg); /** * Removes the given registered pair from the registry. * If the pair is not registered, this method will have * no effect. */ void remove(GPEDParticle* particle, ParticleForceGenerator* fg); /** * Clears all registrations from the registry. This will * not delete the particles or the force generators * themselves, just the records of their connections. */ void clear(); /** * Calls all the force generators to update the forces of * their corresponding particles. */ void updateForces(real duration); };
나는 C++ standard template lirbrary의 growable array인 vector를 사용했다. 그 처음 세 개의 메소드들의 구현은 벡터 자료구에서 대응되는 메소드에 대한 간단한 wrappers이다.
매 프레임 마다, 그 업데이트가 수행되기 전에, 그 force generators들은 호출된다. 그것들은 각 파티클의 acceleration을 계산하기 위해 나중에 사용할 accumulator에 힘을 더할 것이다:
void GPED::ParticleForceRegistry::updateForces(real duration) { Registry::iterator i = registrations.begin(); for (; i != registrations.end(); ++i) i->fg->updateForce(i->particle, duration); }
5.2.3 A Gravity Force Generator
우리는 이전의 중력 구현을 force generator로 대체할 수 있다. 매 프레임마다 constant acceleration을 적용하기 보다, 중력은 각 particle에 부착된 force generator로 나타낼 수 있다. 그 구현은 이것 처럼 보인다:
/** * A force generator that applies a gravitational force. One instance * can be used for multiple particles. */ class ParticleGravity : public ParticleForceGenerator { /** Holds the acceleration due to gravity. */ glm::vec3 gravity; public: /** Creates the generator with the given acceleration. */ ParticleGravity(const glm::vec3& gravity); /** Applies the gravitational force to the given particle. */ virtual void updateForce(GPEDParticle* particle, real duration); };
void GPED::ParticleGravity::updateForce(GPEDParticle* particle, real duration) { // Check that we do not have infinite mass. if (!particle->hasFiniteMass()) return; // Apply the mass-scaled for to the particle particle->addForce(gravity * particle->getMass()); }
그 힘이 updateForce 메소드에 넘겨진 오브젝트의 질량을 기반으로 계산되는 것에 유의해라. 그 클래스에 저장되어있는 유일한 데이터는 중력에 의한 가속도이다. 이 클래스의 한 인스턴스는 어떤 개수의 오브젝트들과도 공유되어질 수 있다.
5.2.4 A Drag Force Generator
우리는 또한 drag에 대한 force generator를 구현할 수 있다. Drag는 한 몸체에 작욯하고 그것의 속도에 의존하는 힘이다. drag의 full model은 우리가 실시간으로 쉽게 구현할 수 있는 좀 더 복잡한 수학을 포함한다. 일반적인 게임 프로그램에서, 우리는 한 몸체에 작용하는 drag가 다음과 같이 주어지는 drag의 간단화된 모델을 사용한다
여기에서 k_1과 k_2는 그 drag force가 얼마나 강한지를 특징화시키는 두 개의 상수이다. 그것들은 보통 "drag coefficients"라고 불려지고, 그래서 그것들은 오브젝트와 재현되는 drag의 유형 둘 다에 의존한다.
그 공식은 복잡한 것처럼 보이지만, 실제로 간단한다. 그것은 그 힘이 그 오브젝트의 속도에 반대방향으로 작용하는 것을 말한다 (이것은 방정식의 -\widehat{\dot{p}} 부분이다: \widehat{\dot{p}}은 파티클의 표준화된 속도이다), 그 오브젝트의 속도와 속도의 제곱에 의존하는 세기와 함께.
k_2 value를 가지는 Drag는 더 높은 속도에서 더 빠르게 커질 것이다. 이것은 차가 무기한으로 가속하는 것을 방지하는 aerodynamic(공기역학) drag의 케이스 해당한다. 느린 속도에서, 차는 거의 공기로부터 어떠한 drag도 느끼지 않지만, 그 속도가 두배 되는 것에 대해, 그 drag는 거의 네 배가 된다. drag generator의 구현은 이것처럼 보인다:
class ParticleDrag : public ParticleForceGenerator { /** Holds the velocity drag coefficient. */ real k1; /** Holds the velocity squared drag coefficient */ real k2; public: /** Creates the generator with the given coefficinets.*/ ParticleDrag(real k1, real k2); /** Applies the drag force to the given particle*/ virtual void updateForce(GPEDParticle* particle, real duration); }; void GPED::ParticleDrag::updateForce(GPEDParticle * particle, real duration) { glm::vec3 force = particle->getVelocity(); // Calculate the total drag coefficient. real dragCoeff = glm::length(force); dragCoeff = k1 * dragCoeff + k2 * dragCoeff * dragCoeff; // Calculate the final force and aplly it. force = glm::normalize(force); force *= -dragCoeff; particle->addForce(force); }
다시 한 번, 그 힘은 그 넘겨진 오브젝트의 특성만을 기반으로 계산된다. 클래스에 저장된 유일한 데이터는 두 상수에 대한 갑싱다. 이전 처럼, 이 클래의 한 instance는 같은 drag coefficients를 갖는 오브젝트의 어떤 개수들과 공유되어질 수 있다.
이 drag model은 상당히 우리가 chapter 3에서 사용했던 간단한 damping보다 더욱 복잡하다. 그것은 골프 공이 비행중에 겪는 drag의 종류를 모델링하기위해 사용되어질 수 있다. flight simulator에 필요한 aerodynamics에 대해, 그러나, 그것은 충분하지 않을지도 모른다; 우리는 chapter 11에서 flight simulator aerodynamics로 돌아 올 것이다.
5.3 Built-In Gravity And Damping
이전에 이야기 된 generators를 사용하여, 우리는 damping과 중력가속도 둘 다를 force generators로 교체 할 수 있다. 이것은 유효한 접근법이고, 많은 다른 엔진들에 의해 사용되는 것이다. 그것은 우리가 damping을 처리하는 특별한 코드를 제거하도록 해준다. 그리고 이것은 우리가 그 오브젝트와 함께 중력가속도를 저장할 필요가 없다는 것을 의미한다; transient(일시적인, 순간적인)한 force accumulation 동안 모든 다른 힘들 사이의 그것은 계산되어질 수 있다.
그것이 간단함에서 몇 가지 장점들을 가지고 있지만, 이것은 내가 사용할 접근법은 아니다. chapter 3에서 우리가 했던 방식으로, 직접적으로 damping과 중력가속도를 적용하는 것이 빠르다. 만약 우리가 매번 그것들에 대한 힘들을 계싼해야 한다면, 우리는 우리가 이미 답을 아는 계산을 수행하는데 추가 시간을 낭비하는 것이다.
이 것을 피하기 위해 나는 damping과 acceleration을 바꾸지 않고 유지한다. 만약 우리가 좀 더 복잡한 drag가 필요하다면, 우리는 damping value를 1에 더가깝게 설정할 수 있고, drag force generator를 추가할 수 있다. 유사하게, 만약 우리가 이색적인 중력 형태가 필요하다면 (궤도 우주선에 대해, 예를들어), 우리는 올바른 행동을 제공하고 중력가속도를 0으로 설정하는 gravity force generator를 만들 수 있다.
5.4 Summary
힘들은 쉽게 그것들의 벡터를 더하여 결합된다. 그리고 그 총 합은 마치 그것이 한 오브젝트에 적용된 유일한 힘인 것처럼 작용한다. 이것이 D'Alembert의 원칙이다. 그래서 그것ㅅ은 우리가 그 힘들이 어떻게 생성되었는에 대한 것을 모른 채로, 어떤 수의 general forces를 지원할 수 있게 한다.
이 책 도처에, 우리는 한 오브젝트에 적용할 힘을 계산하여 물리적 특성의 종류를 재현하는 다양한 종류의 force generators를 볼 것이다. 우리가 이 챕터에서 만든 코드는 우리가 그러한 힘들을 다루게 해준다. 그리고 그것들을 결합하고 적분하기 전에 그것들에 적용한다.
Drag와 gravity는 중요한 force generators이다. 그러나 그것들은 우리가 우리의 particle physics engine에 가졌던 기능성만을 대체한다. mass-aggregate physics engine으로 이동하기 위해서, 우리는 파티클들을 함께 연결시키기 시작할 필요가 있다. Chapter 6는 스프링들과 다른 스프링 같은 연결들을 도입한다. 그리고 우리가 이 챕터에서 만든 force generator를 사용한다.
댓글 없음:
댓글 쓰기