在DirectX SDK中,碰撞檢測的相關函數位於xnacollision.h中。可是如今,前面所實現的相關函數都已經轉移到Windows SDK的DirectXCollision.h中,而且處於名稱空間DirectX內。這裏面主要包含了四種包圍盒(Bounding Volumes),而且是以類的形式實現的:html
除此以外裏面還包含有三角形(射線)與其他物體的碰撞檢測。git
後續的項目將會使用該碰撞庫。github
DirectX11 With Windows SDK完整目錄數組
Github項目源碼ide
歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。函數
一個球體只須要使用圓心座標和半徑就能夠表示。結構體的一部分以下:測試
struct BoundingSphere { XMFLOAT3 Center; // 球體中心座標 float Radius; // 球體半徑 // 構造函數 BoundingSphere() : Center(0,0,0), Radius( 1.f ) {} XM_CONSTEXPR BoundingSphere(const XMFLOAT3& center, float radius ) : Center(center), Radius(radius) {} BoundingSphere(const BoundingSphere& sp ) : Center(sp.Center), Radius(sp.Radius) {} // ... // 靜態建立方法 static void CreateMerged(BoundingSphere& Out, const BoundingSphere& S1, const BoundingSphere& S2 ); static void CreateFromBoundingBox(BoundingSphere& Out, const BoundingBox& box ); static void CreateFromBoundingBox(BoundingSphere& Out, const BoundingOrientedBox& box ); static void CreateFromPoints(BoundingSphere& Out, size_t Count, const XMFLOAT3* pPoints, size_t Stride); static void CreateFromFrustum(BoundingSphere& Out, const BoundingFrustum& fr ); };
其中BoundingSphere::CreateMergerd
靜態方法將場景中的兩個包圍球體用一個更大的包圍球牢牢包住。this
BoundingSphere::CreateFromBoundingBox
靜態方法則是從AABB盒或OBB盒建立出外接包圍球。spa
而後還須要着重說明一下BoundingSphere::CreateFromPoints
靜態方法的使用,由於一般狀況下咱們是用自定義的結構體來描述一個頂點,而後再使用的頂點數組,因此該方法也適用於從頂點數組建立出一個包圍球。code
參數Count
說明了頂點的數目。
參數pPoints
須要填上的是頂點數組第一個元素中位置向量的地址。
參數Stride
便可以說是頂點結構體的字節大小,也能夠說是跳到下一個元素中的位置向量須要偏移的字節數。
下面是一個使用示例:
struct VertexPosNormalTex { XMFLOAT3 pos; XMFLOAT3 normal; XMFLOAT3 tex; }; VertexPosNormalTex vertices[20]; // 省略初始化操做... BoundingSphere sphere; BoundingSphere::CreateFromPoints(sphere, 20, &vertices[0].pos, sizeof(VertexPosNormalTex));
一個物體若擁有AABB盒,那AABB盒的六個面都會和物體牢牢貼靠在一塊兒。它能夠用兩個點描述:Vmax和Vmin。其中Vmax的含義是:分別取物體全部頂點中x, y, z份量下的最大值以構成該頂點。Vmin的含義則是:分別取物體全部頂點中x, y, z份量下的最小值以構成該頂點。
獲取了這兩個點後,咱們就能夠用另外一種表述方式:中心位置C和一個3D向量E,E的每一個份量的含義爲中心位置到該份量對應軸的兩個面的距離(距離是相等的)。
該碰撞庫使用的則是第二種表述方式,但也支持用第一種方式來構建。結構體的一部分以下:
struct BoundingBox { static const size_t CORNER_COUNT = 8; // 邊界點數目 XMFLOAT3 Center; // 盒中心點 XMFLOAT3 Extents; // 中心點到每一個面的距離 // 構造函數 BoundingBox() : Center(0,0,0), Extents( 1.f, 1.f, 1.f ) {} XM_CONSTEXPR BoundingBox(const XMFLOAT3& center, const XMFLOAT3& extents) : Center(center), Extents(extents) {} BoundingBox(const BoundingBox& box) : Center(box.Center), Extents(box.Extents) {} // ... // 靜態建立方法 static void CreateMerged(BoundingBox& Out, const BoundingBox& b1, const BoundingBox& b2 ); static void CreateFromSphere(BoundingBox& Out, const BoundingSphere& sh ); static void XM_CALLCONV CreateFromPoints(BoundingBox& Out, FXMVECTOR pt1, FXMVECTOR pt2 ); static void CreateFromPoints(BoundingBox& Out, size_t Count, const XMFLOAT3* pPoints,size_t Stride ); };
BoundingBox::CreateMerged
靜態方法建立一個最小的AABB盒,可以同時包含這兩個AABB盒。
BoundingBox::CreateFromSphere
靜態方法給球體建立外接立方體包圍盒。
BoundingBox::CreateFromPoints
靜態方法中的參數pt1
和pt2
便可覺得包圍盒某一斜對角線上的兩個頂點,也能夠是一個包含全部點中xyz份量最大值和最小值的兩個構造點。
對某些物體來講,在通過一系列變換後它的包圍盒也須要隨之改變。但例如一些默認狀況下寬度、深度值比高度大得多的物體,好比飛機、書本等,通過變換後它的AABB盒可能會變得特別大,暴露了許多空餘的位置,從而不能很好地將物體包住。
由於AABB盒的邊界是軸對齊的,沒有辦法記錄旋轉屬性。這時候咱們能夠考慮使用OBB盒,除了包含AABB盒應當記錄的信息外,它還記錄了旋轉相關的信息。結構體部分以下:
struct BoundingOrientedBox { static const size_t CORNER_COUNT = 8; // 邊界點數目 XMFLOAT3 Center; // 盒中心點 XMFLOAT3 Extents; // 中心點到每一個面的距離 XMFLOAT4 Orientation; // 單位旋轉四元數(物體->世界) // 構造函數 BoundingOrientedBox() : Center(0,0,0), Extents( 1.f, 1.f, 1.f ), Orientation(0,0,0, 1.f ) {} XM_CONSTEXPR BoundingOrientedBox(const XMFLOAT3& _Center, const XMFLOAT3& _Extents, const XMFLOAT4& _Orientation) : Center(_Center), Extents(_Extents), Orientation(_Orientation) {} BoundingOrientedBox(const BoundingOrientedBox& box) : Center(box.Center), Extents(box.Extents), Orientation(box.Orientation) {} // ... // 靜態建立方法 static void CreateFromBoundingBox(BoundingOrientedBox& Out, const BoundingBox& box ); static void CreateFromPoints(BoundingOrientedBox& Out, size_t Count, const XMFLOAT3* pPoints, size_t Stride ); };
其中BoundingOrientedBox::CreateFromBoundingBox
靜態方法建立出跟AABB盒和同樣的OBB盒,而且單位旋轉四元數是默認的(即沒有產生旋轉)。
爲了描述一個視錐體,一種方式是以數學的形式指定視錐體的六個邊界平面:左/右平面,頂/底平面,近/遠平面。這裏假定六個視錐體平面是朝向內部的。
雖然咱們能夠用六個4D平面向量來存儲一個視錐體,但這仍是有更節省空間的表示方法。
首先在物體座標系中,取攝像頭的位置爲原點,正前方觀察方向爲Z軸,右方向爲X軸,上方向爲Y軸,這樣就能夠獲得右(左)平面投影到zOx平面下的直線斜率爲X/Z(-X/Z),以及上(下)平面投影到zOy平面下的直線斜率爲Y/Z(-Y/Z)。同時也能夠獲得近(遠)平面到原點的距離,也便是對應的Z值。
而後使用一個3D位置向量和單位旋轉四元數來表示視錐體在世界中的位置和朝向。這樣咱們也能夠描述一個視錐體了。
下面展現了視錐體包圍盒結構體的部份內容:
struct BoundingFrustum { static const size_t CORNER_COUNT = 8; XMFLOAT3 Origin; // 攝像機在世界中的位置,物體座標系下默認會設爲(0.0f, 0.0f, 0.0f) XMFLOAT4 Orientation; // 單位旋轉四元數 float RightSlope; // 右平面投影到zOx平面的直線斜率+X/Z float LeftSlope; // 左平面投影到zOx平面的直線斜率-X/Z float TopSlope; // 上平面投影到zOy平面的直線斜率+Y/Z float BottomSlope; // 下平面投影到zOy平面的直線斜率-Y/Z float Near, Far; // Z值對應近(遠)平面到攝像機物體座標系原點的距離 // 構造函數 BoundingFrustum() : Origin(0,0,0), Orientation(0,0,0, 1.f), RightSlope( 1.f ), LeftSlope( -1.f ), TopSlope( 1.f ), BottomSlope( -1.f ), Near(0), Far( 1.f ) {} XM_CONSTEXPR BoundingFrustum(const XMFLOAT3& _Origin, const XMFLOAT4& _Orientation, float _RightSlope, float _LeftSlope, float _TopSlope, float _BottomSlope, float _Near, float _Far) : Origin(_Origin), Orientation(_Orientation), RightSlope(_RightSlope), LeftSlope(_LeftSlope), TopSlope(_TopSlope), BottomSlope(_BottomSlope), Near(_Near), Far(_Far) {} BoundingFrustum(const BoundingFrustum& fr) : Origin(fr.Origin), Orientation(fr.Orientation), RightSlope(fr.RightSlope), LeftSlope(fr.LeftSlope), TopSlope(fr.TopSlope), BottomSlope(fr.BottomSlope), Near(fr.Near), Far(fr.Far) {} BoundingFrustum(CXMMATRIX Projection) { CreateFromMatrix( *this, Projection ); } // ... // 靜態建立方法 static void XM_CALLCONV CreateFromMatrix(BoundingFrustum& Out, FXMMATRIX Projection); }
一般狀況下,咱們會經過傳遞投影矩陣來建立一個包圍視錐體,而不是直接指定上面的這些信息。
對於包圍盒與平面的相交檢測,返回結果使用了枚舉類型PlaneIntersectionType
來描述相交狀況:
enum PlaneIntersectionType { FRONT = 0, // 包圍盒在平面的正面區域 INTERSECTING = 1, // 包圍盒與平面有相交 BACK = 2, // 包圍盒在平面的背面區域 };
上面提到的四種包圍盒都具備重載方法Intersects用於檢測該包圍盒與平面的相交狀況:
PlaneIntersectionType XM_CALLCONV Intersects(FXMVECTOR Plane) const;
正/背面的斷定取決於一開始平面法向量的設定。好比一箇中心在原點,棱長爲2的正方體,與平面-z+2=0(對應4D平面向量(0.0f,0.0f,-1.0f,2.0f), 平面法向量(0.0f,0.0f,-1.0f) )的相交結果爲:物體在平面的正面區域。
對於兩個包圍盒的包含檢測,返回結果使用了枚舉類型ContainmentType
來描述包含狀況:
enum ContainmentType { DISJOINT = 0, // 兩個包圍盒相互分離 INTERSECTS = 1, // 兩個包圍盒有相交 CONTAINS = 2, // 兩個包圍盒存在包含關係 };
這四種包圍盒相互之間都有對應的方法來測試:
ContainmentType Contains(const BoundingSphere& sp) const; ContainmentType Contains(const BoundingBox& box) const; ContainmentType Contains(const BoundingOrientedBox& box) const; ContainmentType Contains(const BoundingFrustum& fr) const;
若是咱們只須要檢查兩個包圍盒之間是否發生碰撞(相交和包含都算),則可使用下面的這些方法。四種包圍盒相互之間都能進行碰撞測試:
bool Intersects(const BoundingSphere& sh) const; bool Intersects(const BoundingBox& box) const; bool Intersects(const BoundingOrientedBox& box) const; bool Intersects(const BoundingFrustum& fr) const;
四種包圍盒都包含下面兩個方法,一個是任意矩陣的變換,另外一個是構造世界矩陣的變換(這裏用BoundingVolume
來指代這四種包圍盒):
void XM_CALLCONV Transform(BoundingVolume& Out, FXMMATRIX M ) const; void XM_CALLCONV Transform(BoundingVolume& Out, float Scale, FXMVECTOR Rotation, FXMVECTOR Translation) const;
要注意的是,第一個參數都是用於輸出變換後的包圍盒,Rotation
則是單位旋轉四元數。
除了包圍球外的其它包圍盒都擁有方法GetCorners
:
void GetCorners(XMFLOAT3* Corners) const;
這裏要求傳遞的參數Corners
是一個能夠容納元素個數至少爲8的數組。
BoundingFrustum::GetPlanes
方法能夠獲取視錐體六個平面的平面向量:
void GetPlanes(XMVECTOR* NearPlane, XMVECTOR* FarPlane, XMVECTOR* RightPlane, XMVECTOR* LeftPlane, XMVECTOR* TopPlane, XMVECTOR* BottomPlane) const;
包圍視錐體在檢測是否包含某一包圍盒的時候內部會調用待測包圍盒的ContainedBy
靜態重載方法,參數爲視錐體提供的六個平面。故下面的方法一般咱們不會直接用到:
ContainmentType XM_CALLCONV ContainedBy(FXMVECTOR Plane0, FXMVECTOR Plane1, FXMVECTOR Plane2, GXMVECTOR Plane3, HXMVECTOR Plane4, HXMVECTOR Plane5 ) const; // Test frustum against six planes (see BoundingFrustum::GetPlanes)
三角形的表示須要用到三個座標點向量,而射線的表示則須要一個Origin
向量(射線起點)和一個Direction
向量(射線方向),其中Direction
是單位向量。
下面這三個經常使用的方法都在名稱空間DirectX::TriangleTests
中(ContainedBy
函數不會直接使用故不列出來):
namespace TriangleTests { bool XM_CALLCONV Intersects(FXMVECTOR Origin, FXMVECTOR Direction, FXMVECTOR V0, GXMVECTOR V1, HXMVECTOR V2, float& Dist ); // 射線與三角形的相交檢測 bool XM_CALLCONV Intersects(FXMVECTOR A0, FXMVECTOR A1, FXMVECTOR A2, GXMVECTOR B0, HXMVECTOR B1, HXMVECTOR B2 ); // 三角形與三角形的相交檢測 PlaneIntersectionType XM_CALLCONV Intersects(FXMVECTOR V0, FXMVECTOR V1, FXMVECTOR V2, GXMVECTOR Plane ); // 平面與三角形的相交檢測 // 忽略... };
其中Dist
返回的是射線起點到交點的距離,若沒有檢測到相交,Dist
的值爲0.0f
四種包圍盒都包含了下面的兩個方法:
bool XM_CALLCONV Intersects(FXMVECTOR Origin, FXMVECTOR Direction, float& Dist) const; // 射線與包圍盒的相交檢測 bool XM_CALLCONV Intersects(FXMVECTOR V0, FXMVECTOR V1, FXMVECTOR V2) const; // 三角形與包圍盒的相交檢測
關於碰撞檢測庫的演示能夠在下面的連接找到,這裏就沒有必要再寫一個演示程序了:
下載(克隆)到本地後找到Collision文件夾,選擇合適的解決方案打開並編譯運行便可。這裏我選擇的是Collision_Desktop_2017_Win10.sln。成功運行的話效果以下:
DirectX11 With Windows SDK完整目錄
歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。