物理引擎Havok教程


物理引擎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/,填寫自己的姓名和郵箱,注冊后即可下載。
     

      下載Havok SDK

 

      Content Tools是內容工具,包括一些3D建模軟件的導出工具。Behavior Tool是給游戲美工或設計師用的角色編輯工具,具有所見即所得的功能。對程序員來講最重要的就是SDK了,我下載的是6.0.0這個版本。因為Intel只開放了物理和動畫兩個組件,所以下載的SDK是只包含這兩個組件,其他的如布料(Cloth)和破壞(Destruction)還是需要付費才能使用。
 Havok SDK使用的是C++語言,開發環境是Visual Studio,我用的版本是2005。

 


      Havok SDK目錄

 

      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設置包含目錄

 

 

       選擇包含文件,添加新的一行,路徑指向Havok SDK安裝目錄的Source目錄。建議建立一個叫HAVOK_HOME的環境變量,這樣可以避免使用絕對路徑。
       庫目錄不能在這里設置,而應該為debug和release版本設置不同的庫包含目錄。因為不論是debug還是release,它們的庫名都是相同的。你可以打開Demo/Demos下面的工程,看看它是如何設置為不同版本設置鏈接包含目錄的。

 

       Havok設置庫目錄

 

三、第一個Havok程序

     這里以SDK自帶的一個控制台演示程序為例,使用Visual Debugger來觀察Havok的具體效果。首先運行Tools/VisualDebugger目錄下的Visual Debugger程序,使用它我們可以觀察到Havok實際運行的效果,而省去渲染步驟,而且可以把場景記錄下來,供以后觀看。演示程序在Demo/StandAloneDemos/ConsoleExampleMt目錄下,這個程序模擬一個快速運動的剛體,撞擊牆壁的效果。運行它,然后就可以在VisualDebugger中看到實際的效果了。

 

       Havok Visual Debugger

 

 好了,第一期教程就是這樣。下期會接觸到具體的編碼問題。如果你有任何問題,歡迎和我交流,我的郵箱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流程

 

圖片取自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程序

 

 

這一期詳細分析我在前面發布的那個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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM