物理引擎Havok教程(一)
搭建開發環境
網上關於Havok的教程實在不多,而且Havok學習起來還是有一定難度的,所以這里寫了一個系列教程,希望能夠幫到讀者。這是第一期。
一、Havok物理引擎簡介
Havok引擎,全稱為Havok游戲動力開發工具包(Havok Game Dynamics SDK),一般稱為Havok,是一個用於物理系統方面的游戲引擎,為電子游戲所設計,注重在游戲中對於真實世界的模擬。使用碰撞功能的Havok引擎可以讓更多真實世界的情況以最大的擬真度反映在游戲中。
開發商Havok成立於1998年,目前Havok物理引擎被超過200款游戲使用,許多電影也應用了這家公司的軟件技術。
2007年9月Havok被Intel收購,為了和NVIDIA的PhysX競爭,Intel在去年的(08年)免費開放了Havok的Physics和Animation組件,內容包括Havok SDK庫、樣例、技術文檔以及支持Maya、3ds Max和Avid XSI等3D建模軟件的格式轉換工具。
按照Havok的授權文檔,即使使用它開發商業游戲也是不需要付費的,這對國內的愛好者應該是一個好消息。
同PhysX相比,個人覺得,Havok無論在穩定性還是功能上,都要略勝一籌。NVIDIA的PhysX在硬件加速上,暫時領先,但隨着AMD加入到Havok硬件加速的開發,未來Havok的功能肯定會更加的強大。
二、Havok開發環境的搭建
1.安裝SDK
首先,到Havok的官網下載SDK,http://software.intel.com/sites/havok/,填寫自己的姓名和郵箱,注冊后即可下載。
Content Tools是內容工具,包括一些3D建模軟件的導出工具。Behavior Tool是給游戲美工或設計師用的角色編輯工具,具有所見即所得的功能。對程序員來講最重要的就是SDK了,我下載的是6.0.0這個版本。因為Intel只開放了物理和動畫兩個組件,所以下載的SDK是只包含這兩個組件,其他的如布料(Cloth)和破壞(Destruction)還是需要付費才能使用。
Havok SDK使用的是C++語言,開發環境是Visual Studio,我用的版本是2005。
Demo目錄下面是SDK的樣例程序和源代碼,Docs是文檔,包括chm和pdf兩種格式。Lib是鏈接庫,庫分為Debug和release及動態鏈接和靜態鏈接。Source下面是SDK的包含文件。Tools下面是工具,包括了Visual Debugger這個可視化調試器。
2.設置Visual Studio
這里以我使用的Visual Studio 2005為例。
頭文件包含目錄的設置。打開Visual Studio 2005,依次選擇工具-》選項-》項目與解決方案-》VC++目錄
選擇包含文件,添加新的一行,路徑指向Havok SDK安裝目錄的Source目錄。建議建立一個叫HAVOK_HOME的環境變量,這樣可以避免使用絕對路徑。
庫目錄不能在這里設置,而應該為debug和release版本設置不同的庫包含目錄。因為不論是debug還是release,它們的庫名都是相同的。你可以打開Demo/Demos下面的工程,看看它是如何設置為不同版本設置鏈接包含目錄的。
三、第一個Havok程序
這里以SDK自帶的一個控制台演示程序為例,使用Visual Debugger來觀察Havok的具體效果。首先運行Tools/VisualDebugger目錄下的Visual Debugger程序,使用它我們可以觀察到Havok實際運行的效果,而省去渲染步驟,而且可以把場景記錄下來,供以后觀看。演示程序在Demo/StandAloneDemos/ConsoleExampleMt目錄下,這個程序模擬一個快速運動的剛體,撞擊牆壁的效果。運行它,然后就可以在VisualDebugger中看到實際的效果了。
好了,第一期教程就是這樣。下期會接觸到具體的編碼問題。如果你有任何問題,歡迎和我交流,我的郵箱songnianhu@163.com,博客blog.csdn.net/shangguanwaner,下期再見。
物理引擎Havok教程(二)
Havok基礎庫簡介
Havok的SDK可以說比較復雜,並不是適合用來學習。拿它用來演示效果的Demo程序的框架來說,它的實現實在是非常的神秘,初學者一開始就接觸海量的代碼,估計會很大的挫傷積極性。所以為了降低大家學習的難度,我在做教程的時候會主要使用實際的代碼來介紹SDK的各種特性,代碼編寫時我會盡量的簡潔和通俗一點。示例代碼我會整理好,提供鏈接,供大家下載。覺得好的話,大家要支持啊!
Havok SDK可以分為三大部分,Havok基礎庫、Havok物理組件、Havok動畫組件。基礎庫,為Havok的其他組件提供了通用功能的支持,Havok物理組件負責實時的剛體模擬,Havok動畫組件負責處理骨骼和角色動畫,與物理組件配合,可以實現強大的角色動畫控制功能。
關於Havok SDK的代碼習慣,這里要說明一下,Havok SDK所有的類名基本上都以hk*開頭,然后后面跟一個字符表示它所屬的組件,例如,hkpWorld,說明它屬於物理組件(Havok Physics),hkaBone,說明它屬於動畫組件(Havok Animation),hk后不跟一個表示組件的字符,則表明它是Havok基礎庫的一部份。
Havok基礎庫
Havok基礎庫定義了一些基本的數據類型和容器類,它還包括與平台無關的接口,用於訪問操作系統的資源,比如內存管理、時鍾等。基礎庫中的許多類都可以修改或者替換,這樣通過提供的源代碼,你可以靈活地擴展基礎庫的功能。不過有些部分,因為編譯進了Havok庫,所以不能被替換,包括那些有內聯函數的容器類,還有config目錄下面的構造配置選項(它保存了在編譯Havok庫時的配置,對它進行任何更改,都需要重新構建整個Havok庫)。
使用基礎庫,只需要簡單的包含hkBase.h即可。hkBase.h內,還定義了一些最基本的數據類型,比如浮點型(hkReal),有符號和無符號整形等等。
1. Havok基本系統
1.1 Havok的初始化
hkBaseSystem類負責創建所有Havok子系統。這些子系統中大部分都是單態類(singleton),各自都有特定的功能,比如內存管理、錯誤處理、流處理。
這些類的主要接口是init和quit方法。調用init方法來初始化Havok子系統。
static hkResult HK_CALL init( hkMemory* memoryManager,
hkThreadMemory* threadMemory,
hkErrorReportFunction errorReportFunction
void* errorReportObject = HK_NULL );
參數memoryManager,是一個將被Havok在內部使用的內存管理器的實現。這個參數允許你在初始化的過程中指定內存管理器而不必重新構建hkBase。Havok本身提供了許多默認的內存管理器的實現,如果沒有特殊要求的話,就可以使用它們。當然,你也可以實現自己的一個內存管理器,Havok推薦使用hkPoolMemory類。這個內存管理器的實現以及其他的可用的實現可以在<hkbase/memory/impl>目錄下面找到。
參數threadMemory,是當前線程的內存管理器,它可以用於優化內存分配和釋放的性能。如果你傳入了HK_NULL作為參數,默認的,hkBaseSystem::init()會為你創建一個hkThreadMemory實例。
errorReportFunction和errorReportObject參數被Havok的錯誤處理器使用,錯誤處理器主要負責處理斷言、錯誤、警告,或者在引擎內部報告這些事件的發生。默認的錯誤處理器是<hkBase/hkerror/hkDefaultError.h>,它只是簡單的調用errorReportFunction打印錯誤消息。
下面舉一個使用hkPoolMemory初始化hkBaseSystem的代碼示例:
#include <Common/Base/System/hkBaseSystem.h> // include for hkBaseSystem
#include <Common/Base/Memory/Memory/Pool/hkPoolMemory.h> // hkPoolMemory
extern "C" int printf(const char* fmt, ...); // For printf, used by the error handler
// Stub function to print any error report functionality to stdout
// std::puts(...) could be used here alternatively
static void errorReportFunction(const char* str, void* errorOutputObject)
{
printf("%s", str);
}
{
...
hkBaseSystem::init( new hkPoolMemory(), HK_NULL, errorReportFunction );
...
}
hkBaseSystem還有一個quit方法,它退出內存系統並銷毀所有的hkSingleton實例。
1.2 Havok多線程模式的初始化
和PhysX不同,Havok現階段采用的是CPU計算,所以Havok采用了多線程來提高性能。
Havok使用的每一個線程,都有它自己的hkThreadMemory內存管理器,必須在線程開始時初始化,線程結束時銷毀。hkThreadMemory可以用於在銷毀和重分配內存時緩存內存塊。
下面的代碼演示在主線程中的初始化。
hkMemory* memoryManager = new hkPoolMemory();
hkThreadMemory threadMemory(memoryManager, 16);
hkBaseSystem::init( memoryManager, &threadMemory, errorReportFunction );
memoryManager->removeReference();
1.2 管理Havok對象
Havok使用引用計數來管理對象。
hkBaseObject是所有Havok類的基類,只有虛函數。hkReferencedObject是hkBaseObject的子類,他是Havok對象管理的最基本單元,剛體(Rigid bodies)、約束(constraints)和動作(actions)都是hkReferencedObject。hkReferencedObject剛創建時,引用計數是1,每當它被引用一次時,引用計數加一,刪除引用時,引用計數減一。當不使用hkReferencedObject時,調用removeReference()引用計數減一。
m_world->addEntity(rigidBody);
rigidBody->removeReference();
2. Havok容器類
Havok提供了兩種容器類,Arrays和Maps。
hkArray是Havok默認的數組類。它和STL的數組類非常相似,但有一些關鍵的不同之處。首先它只支持那些簡單的數據類型,如果你要建立一個對象的數組,那么你應該使用hkObjectArray,而不是hkArray。還有就是hkArray的方法的命名和STL也是不同的,例如STL中是push_back(),而在hkArray中就變成了pushBack()。而且,hkArray不保存元素的次序,例如當調用removeAt()刪除了一個元素,為了提高性能,會用最后一個元素替換這個被刪除的元素,而方法removeAtAndCopy()則可以提供和STL一樣的實現,依然保存元素的次序。另外還有hkInplaceArray,它是hkArray的子類,在性能上要優於hkArray。關於數組類的其它方法你可以查看SDK文檔,這里就不一一介紹了。
3. Havok數學庫
Havok數學庫提供了線性代數的一些相關的數據類型,如向量(vector)、矩陣(matrix)、4元數。hkMath的數據類型在不同的平台上實現是不同的,Havok針對不同的平台作出了相應的優化。
首先來看hkMath提供的基本數據類型,有
hkReal 默認的浮點類型,即float
hkQuadReal 這個類型使用了CPU的SIMD寄存器,占用4個hkReal的空間。
hkSimdReal 當SIMD啟用時,一個單獨的hkReal保存在SIMD寄存器內,x部分是一個hkQuadReal。
SIMD被禁用時,他就僅僅是個簡單的hkReal類型。
其他的hkMath函數還有hkMath::sqrt(),hkMath::sin()等等,可以查看SDK文檔。
SDK中稱與線性代數相關的數據類型為混合類型(Compund Types)。它們有:
hkVector4 這個Havok內通用的向量類。它針對大多數平台進行了相應的優化,而且其他的數學類多數由它組成。每個hkVector4有四個hkReal元素,為了方便進行運算,一般只使用前三個元素x,y,z,后面的w部分,默認值是零。
hkQuaternion 四原數類,可以用來表示旋轉。多數情況下,認為它是一個單位化的四元數。
hkMatrix3 hkReal組成的3X3的矩陣。
hkRotation 這個類保存正交的旋轉矩陣。Havok中,有兩種方法保存旋轉:hkQuaternion和 hkRotation。如果你處理的旋轉是hkVector4,旋轉hkVector4的操作要比旋轉hkQuaternion快。而在需要大量保存旋轉的情況下,使用hkQuaternion效率要更高一些。
hkTransform 這個類表示一個旋轉和平移(rotation,translation)。它分別由一個hkRotation和hkVector4組成。
hkQsTransform 一個分解的Transform(平移向量+四原數+縮放向量)。Havok的動畫系統會經常使用它。
數學庫,就簡單的介紹到這里,它們的使用以及需要注意的地方,我在講分析具體代碼的時候會說。
4. 串行化(Serialization)
所謂串行化就是通過一定的處理將數據轉換為一種可寫的格式。串行化之后,原始數據還應該能夠被精確的還原。就是說串行化之后的數據可以安全的保存到磁盤或通過網絡傳輸。在游戲開發領域,串行化通常用於保存和裝載資源。
Packfiles(這個不知如何翻譯)
使用Havok的串行化工具,可以串行化任意的數據對象。一個對象被串行化了之后,它的所有信息和狀態都會被精確地保存。在需要的時候,這個對象還可以被再次裝載。
串行化后的數據都被保存在一個稱作packfiles的結構中。它既可以保存為XML或者二進制的形式。使用Havok的導出插件,可以從Maya和3D Studio Max中將數據保存到packfiles,然后就可以在Havok的 SDK中轉載了。
向游戲裝載packfiles使用類hkXmlPackfileReader和hkBinaryPackfileReader。XML形式的packfile與平台無關,可以在任意平台上創建和裝載。而二進制形式的packfiles則特定於對應的平台,所以它只能在指定的目標平台上裝載。
Packfiles也可以從一種形式轉換成另一種形式。使用hkXmlPackfileReader讀取XML,再用hkBinaryPackfileWriter轉換成二進制形式。反之,二進制形式的packfile,用hkBinaryPackfileReader讀取數據,hkXmlPackfileWriter轉換數據成XML形式。
4.1 裝載游戲數據
裝載游戲數據的功能,由命名空間hkSerializeUtil和hkLoader工具類提供。它們檢測數據的格式是XML還是二進制形式。並且如果提供的數據是是另一個版本的SDK中建立的,它們還會在裝載的時候將內容更新為最新的版本。
下面的代碼演示了hkLoader工具類的用法:
hkResource* loadedData = hkSerializeUtil::load("filename.hkx");
hkRootLevelContainer* container = loadedData->getContent<hkRootLevelContainer>();
// 裝載后的數據屬於hkResource。必須確保在訪問container的時候,hkResource沒有被銷毀
hkLoader loader;
hkRootLevelContainer* container = loader.load("filename.hkx");
// 裝載后的數據屬於hkLoader,同上,也必須確保在訪問container的時候,hkLoader沒有被銷毀
packfile被裝載之后,還需要做一些附加的處理。例如,需要設置多態對象的虛函數表,未串行化數據的緩存數據或指針也要被初始化。任何需要做最后處理的類都通過被hkTypeInfoRegistry注冊,來完成最后處理。一旦數據裝載完成,packfile讀取器為每個需要做最后處理的的對象調用注冊函數。
4.2 保存游戲數據
游戲中的含有元數據的對象,也可以被串行化並保存為packfile,使用的類是hkXmlPackfileWriter和hkBinaryPackfileWriter。如果碰到指針指向的對象不包含任何元數據這樣的情況,操作會被跳過,並寫入一個空指針。
hkOstream ostream(FILENAME);
kXmlPackfileWriter writer;
writer.setContents(&simpleobject, SimpleObjectClass);
kPackfileWriter::Options options; // use default options
writer.save(ostream.getStreamWriter(), options);
使用hkBinaryPackfileWriter可以為不同的平台導出不同二進制形式的packfile。要實現這樣的操作,設置hkPackfileWriter::Options::m_layout為想要的平台即可。hkStructureLayout包含了若干支持的平台/編譯環境。要讀取的導出的數據,在目標平台上使用hkBinaryPackfileReader,用和上面一樣的方法來讀取數據。
hkOstream ostream(FILENAME);
hkBinaryPackfileWriter writer;
writer.setContents(&simpleobject, SimpleObjectClass);
hkPackfileWriter::Options options;
// 將數據導出為PlayStation®2 gcc3.2 格式
options.m_layout = hkStructureLayout::Gcc32Ps2LayoutRules;
writer.save(ostream.getStreamWriter(), options);
5. 快照工具集
Havok還提供了快照工具集(snapshot utility),它可以方便的保存world,很好地隱藏了底層的細節。
下面的代碼演示如何保存world:
// Save the world into the file at "path"
static hkResult saveWorld( hkpWorld* world, const char* path, bool binary )
{
hkOstream outfile( path );
return hkpHavokSnapshot::save(world, outfile.getStreamWriter(), binary);
}
注意,在保存文件的過程中,如果碰到不明白的對象,會在控制台上打印警告消息。
裝載快照,和保存差不多,代碼如下:
// Loads snapshot at "path".
static hkpWorld* loadWorld( const char* path, hkPackfileReader::AllocatedData** allocsOut )
{
hkIstream infile( path );
hkpPhysicsData* physicsData = hkpHavokSnapshot::load(infile.getStreamReader(), allocsOut);
return physicsData->createWorld();
}
6. 多線程
Havok被專門設計成運行於多線程環境。這一小節將簡單的介紹Havok的多線程的工作原理,以及如何在物理模擬時使用多線程。要實現Havok的多線程運行,需要兩個類,hkJobQueue和hkJobThreadPool類。
hkJobQueue類。工作隊列是所需完成job的核心容器。job是一項可以被任意線程處理的工作的單元。一個線程可以向隊列添加job,也可以向隊列請求job。
hkJobQueue::processAllJobs()可以被多個線程同時調用,然后每個線程都會開始處理job知道隊列中沒有任何job為止。hkJobThreadPool是一個抽象類,它允許多線程從工作隊列處理job。典型的,在整個應用程序的生命周期內,只創建一個hkJobThreadPool的實例。這樣就保證了始終有一組線程在運行。調用hkJobThreadPool::processAllJobs()方法,會導致它簡單的在所有的工作線程上調用hkJobQueue::processAllJobs()方法。這個函數直到所有工作都完成之后才會返回。
Havok的物理、動畫、碰撞查詢、布料、Bihavior都支持在多線程環境下執行。理論上,向hkJobQueue添加job,調用processAllJobs(),可以並行的進行以上所有的計算。但僅僅是理論上而,其中還有許多同步的問題,導致完全並行是不可能的。
我們這里只介紹如何在多線程環境下運行Havok的物理模擬。其他組件與之類似。
6.1 物理模擬的多線程
Havok SDK隨帶了一個單獨的示例,演示了如何進行物理模擬的多線程計算。代碼在Demo/StandAloneDemos/ConsoleExampleMt目錄下。
要啟用多線程物理模擬,你在創建world的時候需要將hkpWorldCinfo::m_simulationType設置成hkpWorldCinfo::SIMULATION_TYPE_MULTITHREAD。代碼演示如下:
...
// Create thread pool and job queue
hkJobThreadPool* threadPool = new hkCpuJobThreadPool( threadPoolCinfo );
hkJobQueue* jobQueue = new hkJobQueue();
// Create a physics world as usual.
//Make sure the simulation type in worldCinfo is SIMULATION_TYPE_MULTITHREADED
hkpWorld* physicsWorld = new hkpWorld(worldCinfo);
// Register physics job handling functions with job queue
physicsWorld->registerWithJobQueue( &jobQueue );
while( simulating )
{
// Step the world using this thread, and all the threads or SPUs in the thread pool
physicsWorld->stepMultiThreaded( &jobQueue, threadPool, timestep );
}
stepMultiThreaded()內部調用hkJobQueue::processAllJobs()和hkJobThreadPool::processAllJobs()完成實際的多線程運算。
好了,第二期教程到此結束,因為Havok SDK的基礎庫,比較大,所以我只是取了其中比較重要,經常用到的內容,拿出來講。具體的可以查看SDK的文檔。
物理引擎Havok教程(三)
物理組件初探
作者:上官婉兒
這一期將初步介紹Havok最重要的一個組件Physics組件。這個組件主要做剛體物理學的模擬。這一期講一下它的工作原理和流程。
1.它是如何工作的?
在游戲中使用Havok,一般遵循這樣的步驟,先創建好一個連續的模擬並准備好渲染系統,然后進行一次模擬,渲染器取得模擬的結果,在一秒內進行若干次的渲染。在Havok的SDK中,就稱這樣的一個時間片叫一幀。我們來看一下,Havok的工作流程。假設所有工作都已經初始化好了,Havok在每一幀的工作流程如下所示:
圖片取自Havok的SDK文檔。
下面分別介紹各個步驟。
1.1 建立模擬區域(Set up simulation islands)
Havok使用模擬區域來划分一組一組的物理對象。通過將所有整個物理世界的對象划分為各個小的區域來進行模擬,可以更好的利用系統的CPU和內存資源。而且,如果某個區域內的所有對象都已失效(deactivated),那么簡單的失效整個區域即可,以后就不必再參與模擬。模擬區域是由系統自動設置的,用戶不必自己建立。
模擬區域還管理Havok的失效系統(deactivation system),任何停止移動的對象,都符合失效的條件,一個失效的對象就不必再模擬了,這樣可以節約CPU資源。每一個對象都有一個失效器(deactivator),通過查詢自己的狀態,它可以告訴系統,是否可以安全的使自己失效。
如果使用默認的失效類型,如果一個模擬區域內的所有對象都符合失效的條件,整個區域就會被失效。而一旦區域內的某個剛體重新有效,這個區域內的所有其他剛體也會被重新有效。
下面的步驟都是基於每一個模擬區域來說的。
1.2 施加作用(Applying actions)
通過施加作用,你可以在模擬的過程中控制物體的狀態。每一個模擬步驟,系統都會調用你向物理世界中添加的每一個作用的applyAction()方法。使用Havok提供的接口,可以實現自定義的作用(actions)。
1.3 建立約束(Setup Constraints)
場景中的約束都是經過處理的。包括接觸約束,它可以防止兩個物體相互穿插。你也可以在物體之間指定特定的約束類型,比如,鉸鏈或者球狀關節(hinges,ball-and-socket joints)。當然你也可以創建自定義的約束。
1.4 Solve
在剛體世界里,物體是不應該相互穿插的,如果出現,就是一個錯誤。為了避免這樣的錯誤,我們需要移動一下物體。而解決器(Solver)的職責就是計算避免這個錯誤所需要的移動量,並把這個信息傳遞給下一個步驟,整合。
1.5 整合(Integration)
整合器(integrator)計算新的運動狀態(初始和終了的位置的旋轉,速度,加速度等等),對每個模擬的對象,會考慮解決器提供的信息。然后對象新的位置和旋轉就可以傳遞給游戲,更新游戲對象。
1.6 碰撞檢測(Collision detection)
在Havok中,碰撞檢測被分成了三個階段。broadphase碰撞檢測,它可以快速地得到一個近似的結果,它查找所以潛在的可能碰撞的一對對的物體。midphase碰撞檢測,它從潛在的碰撞中提取更多的細節信息。最后是narrowphase碰撞檢測,它確認這些成對的物體中到底哪些實際碰撞了。如果發現了碰撞,narrowphase就用這些信息,創建碰撞代理(collision agents)和碰撞接觸點。這些碰撞代理和接觸點,會提供給接下來的一個模擬幀使用。
每個物理對象都有hkpCollidable成員,它有一個hkpShape成員,定義用於碰撞檢測的對象的形狀。物體可以有各種形狀,從簡單的方體、球體到更復雜的混合形狀以及網格。
說明,這里我講SDK中的專用術語都翻譯成了中文,翻譯得不一定准確,所以每一個術語后面都有它的英文原詞,方便你查看SDK文檔。
有任何意見和建議,歡迎聯系,我的郵箱songnianhu@163.com
這一期詳細分析我在前面發布的那個Havok實例的代碼。運行效果和代碼下載請參看我之前的一篇文章 《物理引擎Havok的一個簡單實例(使用Ogre渲染)》。最新的代碼可以在這里下載: http://code.google.com/p/ogrehavok/downloads/list
在例子中,我用了開源的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的場景節點。
具體請看代碼,注釋寫的很清楚。
好了就是這樣,文章寫的比較糟糕,見諒。有問題,可以和我聯系,songnianhu@163.com
再分享一下我老師大神的人工智能教程吧。零基礎!通俗易懂!風趣幽默!還帶黃段子!希望你也加入到我們人工智能的隊伍中來!https://blog.csdn.net/jiangjunshow