1. 程式人生 > >物理引擎Havok教程

物理引擎Havok教程

 在例子中,我用了開源的Ogre作為渲染引擎。如果你想撇開圖形渲染,只看Havok模擬的程式碼,可以參看Havok中自帶的一個例項StandAloneDemos。

一、類HavokSystem
 例子中類HavokSystem封裝了Havok初始化和執行程式碼。它初始化基本庫和多執行緒模擬,並建立物理世界。宣告如下:

class HavokSystem
{
public:
 HavokSystem(void);
 

 ~HavokSystem(void);
 //建立hkpWorld
 virtual bool createHavokWorld(hkReal worldsize);
 //初始化VDB
 virtual bool InitVDB();
 //建立物理場景
 virtual void createPhysicsScene();
 void   setGroundSize(hkReal x,hkReal y,hkReal z);
 void   setGroundPos(hkReal x,hkReal y,hkReal z);

 //step simulation
 virtual void simulate();

 void   setup();

 //Physics
 hkpWorld*     m_World;

protected:
 //成員變數
 hkPoolMemory*    m_MemoryManager;
 hkThreadMemory*    m_ThreadMemory;
 char*      m_StackBuffer;
 int       m_StackSize;
 //多執行緒相關
 hkJobThreadPool*   m_ThreadPool;
 int       m_TotalNumThreadUsed;
 hkJobQueue*     m_JobQueue;
 
 //VDB相關
 hkArray<hkProcessContext*> m_Contexts;
 hkpPhysicsContext*   m_Context;
 hkVisualDebugger*   m_Vdb;
 hkpRigidBody*    m_Ground;   //地面,即靜態剛體
 hkVector4     m_GroundSize;  //地面尺寸
 hkVector4     m_GroundPos;  //地面位置
 // 省去無關細節
 ...
};

1.初始化多執行緒
 HavokSystem類的建構函式實現了多執行緒模擬的初始化,來看具體程式碼:
 首先,要初始化Havok的基本庫。初始化記憶體管理器,然後呼叫hkBaseSystem的init方法。注意,init呼叫後,m_MemoryManager被hkBaseSystem擁有,所以要記住將它的引用計數減一。

 m_MemoryManager = new hkPoolMemory();
 m_ThreadMemory = new hkThreadMemory(m_MemoryManager);
 hkBaseSystem::init(m_MemoryManager,m_ThreadMemory,errorReport);
 
 m_MemoryManager->removeReference();

 接著初始化堆疊。

 m_StackSize = 0x100000;
 m_StackBuffer = hkAllocate<char>(m_StackSize,HK_MEMORY_CLASS_BASE);
 hkThreadMemory::getInstance().setStackArea(m_StackBuffer,m_StackSize);

 最後,真正的初始化多執行緒。通過hkHardwareInfo,可以獲取與系統執行和硬體相關的資訊,比如執行緒數,核心數等。這裡只是用它獲取在多執行緒模擬中使用的執行緒的數目。Havok在建立物件時,一般都使用*Cinfo的形式指定建立的引數,例如建立hkpWorld,就先填充引數到hkpWorldCinfo,然後用new hkpWorld(hkpWorldCinfo&)建立hkpWorld物件,其它與此類似。
 
 int m_TotalNumThreadsUsed;

 hkHardwareInfo hwInfo;
 hkGetHardwareInfo(hwInfo);
 m_TotalNumThreadsUsed = hwInfo.m_numThreads;

 hkCpuJobThreadPoolCinfo,CPU執行緒池的資訊,如執行緒的數目,每個執行緒的堆疊的大小等。它的成員m_numThreads是可以使用的執行緒的數目,要減一,因為主執行緒不在計算內。m_timerBufferPerThreadAllocation,它是為了儲存timer的資訊,而在每個執行緒中分配的緩衝區的尺寸。如果使用VDB(視覺化偵錯程式),建議設為2000000。

 hkCpuJobThreadPoolCinfo gThreadPoolCinfo;
 gThreadPoolCinfo.m_numThreads = m_TotalNumThreadsUsed-1;
 gThreadPoolCinfo.m_timerBufferPerThreadAllocation = 200000;
 m_ThreadPool = new hkCpuJobThreadPool(gThreadPoolCinfo);

 建立工作佇列。這在前面介紹基本庫時有介紹,可以複習一下。

 hkJobQueueCinfo info;
 info.m_jobQueueHwSetup.m_numCpuThreads = m_TotalNumThreadsUsed;
 m_JobQueue = new hkJobQueue(info);

 ...//省去無關細節

 以上就是Havok初始化多執行緒模擬的過程,完整的程式碼請檢視原始碼。

2.建立物理世界
 類的createHavokWorld方法負責建立物理世界。一個Havok的模擬可以有一個或多個Havok世界,表現為hkpWorld的例項。它是一個容器,用來承載所有要模擬的物理物件。它有一些基本屬性,比如重力,Slover等,具體的可以檢視文件。下面演示如何建立hkpWorld例項。

 hkpWorldCinfo成員m_simulationType,這裡是多執行緒模擬,所以用SIMULATION_TYPE_MULTITHREADED,另外常用的還有SIMULATION_TYPE_CONTINUOUS,表示連續模擬。m_broadPhaseBorderBehaviour,它指定hkpWorld BroadPhase(粗略檢測階段)的行為,這裡設定為BROADPHASE_BORDER_REMOVE_ENTITY,表示當物件超出hkpWorld的尺寸時,就將這個實體物件刪除。方法setBroadPhaseWorldSize()用於設定hkpWorld的尺寸,引數是一個hkVector4型別的向量。


 hkpWorldCinfo worldInfo;
 worldInfo.m_simulationType = hkpWorldCinfo::SIMULATION_TYPE_MULTITHREADED;
 worldInfo.m_broadPhaseBorderBehaviour = hkpWorldCinfo::BROADPHASE_BORDER_REMOVE_ENTITY;
 //設定world尺寸
 worldInfo.setBroadPhaseWorldSize(worldsize);
 //worldInfo.m_gravity = hkVector4(0.0f,-16.0f,0.0f);

 建立hkpWorld,然後註冊碰撞代理(Collision Agent),需要注意的是,在多執行緒模擬時,hkpWorld提供了markForWrite和unMarkForWrite方法,這樣一種類似於臨界區的機制來防止競爭的發生。每當要修改hkpWolrd時,都要記得使用這兩個方法,或者與之功能類似的lock()和unlock()。

 m_World = new hkpWorld(worldInfo);
 m_World->m_wantDeactivation = false;
 m_World->markForWrite();
 //註冊碰撞代理
 hkpAgentRegisterUtil::registerAllAgents(m_World->getCollisionDispatcher());
 m_World->registerWithJobQueue(m_JobQueue);

 m_World->unmarkForWrite();

3.建立物理場景
 類的createPhysicsScene負責建立物理場景。這裡建立了地面。
 剛體的建立,通過一個叫hkpRigidBodyCinfo的類,它指定了剛體的各種引數,如形狀(shape)、位置、運動型別等。以下演示瞭如何建立一個靜態的剛體。這裡是作為地面。注意,在修改hkpWorld之前,要先markForWrite。

 m_World->markForWrite();
 //建立Ground
 hkpConvexShape* shape = new hkpBoxShape(m_GroundSize,0.05f);

 hkpRigidBodyCinfo ci;

 ci.m_shape = shape;
 ci.m_motionType = hkpMotion::MOTION_FIXED;
 ci.m_position = m_GroundPos;
 ci.m_qualityType = HK_COLLIDABLE_QUALITY_FIXED;

 建立剛體,然後新增的物理世界。hkpWorld呼叫addEntity之後,這個剛體就屬於hkpWorld了,不要忘了刪除一次引用。shape同理。


 m_Ground = new hkpRigidBody(ci);
 m_World->addEntity(m_Ground);
 shape->removeReference();
 m_World->unmarkForWrite();

4.開啟模擬
 simulate方法負責整個模擬狀態的更新,每一幀或者每幾幀會呼叫它一次。為了簡單,我把模擬的頻率固定在了60幀,即1.0/60,程式碼如下:


 兩次模擬的時間間隔,我固定死了,為1.0/60,這個值也是SDK推薦的值。你還可以用更小的1.0f/30,這樣可以獲得更高的效率。

 hkReal timestep = 1.0f/60.0f;
 hkStopwatch stopWatch;
 stopWatch.start();
 hkReal lastTime = stopWatch.getElapsedSeconds();

 最重要的一次呼叫,進行了一次多執行緒模擬。


 m_World->stepMultithreaded(m_JobQueue,m_ThreadPool,timestep);

 (...省略無關細節)
 
 hkMonitorStream::getInstance().reset();
 m_ThreadPool->clearTimerData();
 
 在這裡等待,以固定幀率。
 while(stopWatch.getElapsedSeconds()<lastTime+timestep);
 lastTime += timestep;

二、繫結Havok和Ogre
 為了封裝Havok和Ogre,建立了一個OgreHavokBody類,它將Havok的剛體物件與Ogre的場景節點封裝在一起,簡化了操作。這個類內部會根據剛體的狀態改變而自動同步它的渲染物件。做這項工作的是它的update方法。每一幀,這個方法都會被呼叫,它讀取Havok剛體的位置和旋轉,然後用這些資訊更新Ogre的場景節點。
 具體請看程式碼,註釋寫的很清楚。

 
 好了就是這樣,文章寫的比較糟糕,見諒。有問題,可以和我聯絡,[email protected]