군집 시뮬레이션 Flock Simulation
이번에는 조그만 적 우주선 여러 대가 큰 모선을 공격하는 동작을 시뮬레이션하는 방법에 대해 알아보겠습니다. 군집 시뮬레이션(Flock Simulation)이라고 불리는 이 기법은 새의 무리나, 파리 혹은 물고기의 움직임을 시뮬레이션 하기 위해 만들어진 기법입니다. 이 기법은 1987년 에 Craig W. Reynolds라는 사람이 쓴 글이 Computer Graphics 학회지에 게시되면서 인지도를 얻게 되었는데요 그 글은 아래를 방문하시면 읽어보실 수 있습니다.
Craig W. Reynolds, "Flocks, Herds, and Schools:A Distributed Behavioral Model", 1987, Computer Graphics, 21(4), July 1987, pp. 25-34.[http://www.cs.toronto.edu/~dt/siggraph97-course/cwr87/]
프로그래밍에 들어가기 전에 아래의 그림처럼 Blender에서 장면을 세팅합니다. 모선으로 사용할 큰 함선 한 대와 작은 우주선 여러 대를 만듭니다.
그리고 .dae 파일로 익스포트 합니다. 익스포트에 대한 사항은 이전 문서인 "OpenGL3.2 모델링 프로그램에서 만든 오브젝트(장면) 로딩하기"를 참고해 주세요.
이제 모선과 적 함정을 나타낼 클래스가 필요한데요. CShip이라는 클래스를 만들었고 이것을 상속받은 CMyShip과 CEnemyShip이 각각 모선과 적 함정을 표현합니다.
[ship.h]
#pragma onceclass CObj;#include "Transform.h"class CShip : public CTransform{public:CShip();~CShip(void);void SetMaxHP(float maxhp) {m_maxHP = maxhp;}void SetCurHP(float curhp) {m_curHP = curhp;}void LoadProperty(const char* filepath);void Damage(float amount);BOOL IsAlive() { return m_bAlive; }protected://virtual void Destroyed();protected:BOOL m_bAlive;float m_curHP;float m_maxHP;};
[myship.h]
#pragma once#include "Ship.h"class CMyShip : public CShip{public:CMyShip();~CMyShip(void);void Update(float fElapsed);void Draw(const glm::mat4& view);private://UINT m_numFirePoint;//std::vector<CFirePoint*> m_vFirePoints;};extern CMyShip* g_pMyShip;
[enemyship.h]
#pragma once#include "Ship.h"class CEnemyShip : public CShip{public:CEnemyShip();~CEnemyShip(void);protected://virtual void Destroyed();};typedef std::vector<CEnemyShip*> ENEMYSHIPVECTOR;extern ENEMYSHIPVECTOR g_vEnemyShips;
각 클래스들이 하는 일이 그렇게 많지 않아서 복잡하진 않습니다. 하지만 CShip이 상속받는 CTransform클래스는 조금 복잡한데요 왜냐하면 이 클래스가 함선의 이동에 관련된 일들을 해주기 때문입니다.
[CTransform.h]
#pragma once#include "Obj.h"class CTransform : public CObj{public:CTransform(void);~CTransform(void);void Update(float fElapsedTime);virtual void BuildBuffer(const aiScene* scene, const aiNode* nd, CObj* parentObj);void SetAccel(float fAccel) { m_fCurAccel = m_fDestAccel = fAccel; }void SetDeaccel(float fDeaccel) { m_fDeaccel = fDeaccel; }void SetRotationSpeed(float fRotSpeed) { m_fCurRotSpeed = m_fDestRotSpeed = fRotSpeed; }void SetDestDir(const glm::vec3& vec) { m_DestDir = vec; } // vec must be normalized.void SetMaxSpeed(const float fMaxSpeed) { m_fMaxSpeed = fMaxSpeed; }void SetDestSpeed(const float fDestSpeed);void SetCurSpeed(const float fDestSpeed);void ClearTransform();void RecalcInitialMat();const glm::vec3& GetCurDir() { return m_CurDir; }virtual const glm::vec3& GetDestDir() { return m_DestDir; }float GetCurSpeed() { return m_fCurSpeed; }virtual float GetRotSpeed() { return m_fCurRotSpeed; }virtual const glm::quat& GetCurRot() { return m_CurRot; }const glm::vec3& GetPos() const { return m_CurPos; }const glm::vec3& GetCurScale() const { return m_CurScale; }void SetCurRot(glm::quat& q) { m_CurRot = q; }private:glm::vec3 m_CurPos; // 0.0, 0.0, 0.0glm::vec3 m_DestPos; // 0.0, 0.0, 0.0glm::vec3 m_CurScale; // 1.0, 1.0, 1.0glm::vec3 m_DestScale; // 1.0, 1.0, 1.0glm::quat m_CurRot;glm::quat m_DestRot;glm::vec3 m_CurDir; // 0.0, 1.0, 0.0glm::vec3 m_DestDir; // 0.0, 1.0, 0.0float m_fCurSpeed; //0float m_fDestSpeed; //0float m_fMaxSpeed; //1float m_fCurAccel; //1float m_fDestAccel; //1float m_fDeaccel; //1float m_fCurRotSpeed; //0float m_fDestRotSpeed;//0 float m_fCurRotAccel; //1float m_fDestRotAccel; //1};
[FlockSystem.h]
Rule1은 함선들을 너무 분산되지 않도록 해줍니다. 다시 말해서, 모든 보이드들의 위치를 고려해서 중앙지점을 찾아 그곳의 좌표를 리턴해 줍니다.
#pragma once#include <vector>#include "Boid.h"class CShip;class CFlockSystem{public:CFlockSystem(void);~CFlockSystem(void);void AddBoid(CShip* ship);void AddTarget(CShip* ship);void Update();glm::vec3 rule1(const CBoid* b);glm::vec3 rule2(const CBoid* b);glm::vec3 rule3(const CBoid* b);glm::vec3 rule4(CBoid* b);void DeleteBoid(CShip* ship);private:typedef std::vector<CBoid*> BOIDVECTOR;BOIDVECTOR m_boids;BOIDVECTOR m_targets;};
AddBoid()를 호출함으로써 적 함선들을 Boid로 추가 시킬 수 있습니다. AddTarget()을 호출함으로써 모선을 등록할 수 있습니다. 보이드들은 이 타겟을 중심으로 움직임을 보이게 됩니다. 함수 rule1 부터 4까지는 보이드들이 어떤 움직임을 보일지를 결정합니다.
[rule1]
glm::vec3 CFlockSystem::rule1(const CBoid* b) // keep together{glm::vec3 pc;UINT count=0;glm::vec3 result;BOIDVECTOR::iterator it = m_boids.begin(), itend = m_boids.end();for (; it!=itend; it++){if (*it != b){count++;pc += (*it)->GetPos();}}if (count > 0){pc /= count;result = pc - b->GetPos();}return result;}
[rule2]
glm::vec3 CFlockSystem::rule2(const CBoid* b) // preventing collision with each other{glm::vec3 c;BOIDVECTOR::iterator it = m_boids.begin(), itend = m_boids.end();for (; it!=itend; it++)if (*it != b)if (glm::distance((*it)->GetPos(), b->GetPos()) < 1)c -= (*it)->GetPos() - b->GetPos();return c;}
Rule 2는 서로간의 충돌을 방지해 주는 역할을 합니다.
[rule3]
glm::vec3 CFlockSystem::rule3(const CBoid* b) // go to target{glm::vec3 result = b->GetTarget() - b->GetPos();return glm::normalize(result);}
Rule 3는 함선을 타겟으로 이동하게 끔 해줍니다.
[rule4]
glm::vec3 CFlockSystem::rule4(CBoid* b) // prevent collision with the Ship{glm::vec3 c;glm::vec3 away;UINT count=0;;float fDist;BOIDVECTOR::iterator it = m_targets.begin(), itend = m_targets.end();for (; it!=itend; it++){fDist = glm::distance((*it)->GetPos(), b->GetPos());if (fDist < 4.0f){count++;away = b->GetPos() - (*it)->GetPos();away *= -fDist/4.0f+1.0f;c += away;if (fDist<3.5f){b->SetAwayPhase(TRUE);}}}return c;}
Rule 4는 적 함선과 모선이 충돌하는 것을 방지해 줍니다. 적 함선이 모선에 다다르면 모선으로부터 멀어지도록 Away Phase를 설정합니다. 이 Phase는 랜덤한 초 동안 유지되고 모선으로부터 멀어지게 됩니다.
이 룰들은 Update함수에서 결합됩니다.
void CFlockSystem::Update(){BOIDVECTOR::iterator it = m_boids.begin(), itend = m_boids.end();for (; it!=itend; it++){glm::vec3 keepTogether, KeepDistance, gotoTargetNormalized, PrevectCollisionWithTarget;keepTogether = rule1(*it);KeepDistance = rule2(*it);gotoTargetNormalized = rule3(*it);PrevectCollisionWithTarget = rule4(*it);(*it)->AddDir(keepTogether*0.01f + KeepDistance * 0.1f + gotoTargetNormalized * 2.0f + PrevectCollisionWithTarget * 3.0f);}it = m_boids.begin(), itend = m_boids.end();for (; it!=itend; it++){(*it)->Apply();}}
각 룰은 가중치 값을 가지고 있는데요 예를 들어 rule 1의 가중치 값은 0.01입니다. 이것은 매우 작은 값인데요 왜냐하면 적 함선들이 너무 뭉쳐서 다니면 모선에 의해 파괴되기 쉽기 때문입니다. 그래서 이 값을 작은 값으로 설정해 그들이 너무 뭉치지 않게 해 주는 것입니다.
아래는 결과 동영상입니다.
완성된 군집 시뮬레이션.
거대 모선에 레이저를 장착한 후.
거대한 모선을 공격하고 있는 소형 함선들이 상상 되시나요?

댓글
댓글 쓰기