github工程。

如何進行相交測試

在第一期文章中,我們最後留下來一個問題:

如何判斷一條射線和哪些物體相交

這個問題分為兩步來解決:

使用空間場景劃分找到可能相交的物體(粗測階段)

。(找到1,2,3這三個物體)

對上一步找到的物體進行逐個遍歷,判斷出是否相交(精測階段)

。(對1,2,3進行遍歷,最終只有1是相交的)

【物理篇】從零搭建2D物理系統③——物體相交測試(完結)

粗測階段

粗測階段首先需要用一個AABB把射線包裹起來。這一步很簡單,注意當射線完全垂直或者水平時需要有一個

最小寬度和最小高度

///

/// CreateRect By a 2 vector2 point

///

public

static

Rect

CreateRect

Vector2

start

Vector2

end

{

var

segment

=

end

-

start

var

rect

=

new

Rect

();

rect

x

=

Mathf

Min

start

x

end

x

);

rect

y

=

Mathf

Min

start

y

end

y

);

rect

width

=

Mathf

Abs

segment

x

);

rect

height

=

Mathf

Abs

segment

y

);

if

rect

width

==

0f

{

rect

width

=

MINRECTWIDTH

}

if

rect

height

==

0f

{

rect

height

=

MINRECTHEIGHT

}

return

rect

}

第二步是遍歷四叉樹,有

按廣度遍歷和按深度遍歷

兩種方法,我這裡採用按廣度遍歷。考慮到某些同學可能不知道如何按廣度遍歷,我這裡解釋一下: 比如如下二叉樹,按廣度遍歷就是8,6,10,5,7,9,11。而按深度遍歷有前序遍歷、中序遍歷、後序遍歷,這裡就不展開了。廣度遍歷的做法就是

使用一個佇列來儲存將要遍歷的節點

。遍歷到8時,把左節點6和右節點10放到佇列,遍歷到6時把左節點5和右節點7放到佇列,當一個節點是葉節點時不處理,不斷迴圈,直到佇列裡沒有東西。

【物理篇】從零搭建2D物理系統③——物體相交測試(完結)

在我們這裡鬆散四叉樹的情況,為了剪掉不必要的分支,

當節點的鬆散範圍不與當前檢測的AABB相交時或者它和它的所有子節點包含的物體個數為0時

,我們可以不要考慮這個節點。

最後程式碼如下:

//QuadTree。cs

public

List

<

IQuadTreeItem

>

GetItems

Rect

rayRect

{

_cacheItemsFound

Clear

();

_traverseNodeQueue

Clear

();

AddNodeToTraverseList

_root

rayRect

);

while

_traverseNodeQueue

Count

>

0

{

var

currentChild

=

_traverseNodeQueue

Dequeue

();

foreach

IQuadTreeItem

item

in

currentChild

items

{

if

item

rect

Intersects

rayRect

{

_cacheItemsFound

Add

item

);

}

}

if

currentChild

isLeaf

==

false

{

foreach

var

node

in

currentChild

childNodes

{

AddNodeToTraverseList

node

rayRect

);

}

}

}

return

_cacheItemsFound

}

private

void

AddNodeToTraverseList

QuadTreeNode

node

Rect

rayRect

{

if

node

totalItemsCount

>

0

&&

node

looseRect

Intersects

rayRect

{

_traverseNodeQueue

Enqueue

node

);

}

}

Rect.Intersets

是我寫的擴充套件方法,用來判斷兩個AABB是否相交,原理也很簡單,比較2個AABB的xMin,xMax,yMin,yMax。

public

static

bool

Intersects

this

Rect

rect

Rect

target

{

if

rect

yMin

>

target

yMax

{

return

false

}

if

rect

yMax

<

target

yMin

{

return

false

}

if

rect

xMax

<

target

xMin

{

return

false

}

if

rect

xMin

>

target

xMax

{

return

false

}

return

true

}

在碰撞檢測領域,AABB和AABB是否相交是最簡單的問題了,往上的還有

三角形和三角形相交,多邊形和多邊形相交,圓和多邊形相交等等

。它們會用到一個

凸體基本理論:若兩個凸體不相交,則必定存在一個平面,使兩個凸體分別處於兩邊。

這些平面通常是物體的切平面,而取平面的法向量做軸,將物體投影與這些軸上,如果有一個軸上的投影存在重疊,則兩個凸體必然相交

。這個軸就叫分離軸,這個定理叫

分離軸定理

(這些內容屬於擴充套件知識,平常基本用不到,但是如果面試問到了就是你裝逼的機會了。)

具體可以看這篇文章:《碰撞檢測之分離軸定理演算法講解》關於碰撞檢測更多的知識,可以看

《實時碰撞檢測演算法技術》

這本書。

(左圖不相交,右圖相交)

【物理篇】從零搭建2D物理系統③——物體相交測試(完結)

精測階段

經過上一步,我們可以知道射線可能相交的物體有1,2,3這三個物體。接下來逐個判斷射線是否與這三個物體是否相交。

【物理篇】從零搭建2D物理系統③——物體相交測試(完結)

那麼,

如何判斷射線與AABB是否相交呢?

這個問題可以轉化成

射線與AABB的四條邊是否相交

,進而轉化成

射線與線段是否相交

。 首先根據射線起點和終點求出射線方程:

var

destPoint

=

origin

+

dir

*

distance

//ax + by + c = 0

var

a

=

destPoint

y

-

origin

y

var

b

=

origin

x

-

destPoint

x

var

c

=

destPoint

x

*

origin

y

-

destPoint

y

*

origin

x

【物理篇】從零搭建2D物理系統③——物體相交測試(完結)

然後我以AABB的左邊舉例,把xMin代入直線方程,如果對應的y值處於yMin和yMax之間說明有交點,注意b不能等於0,因為需要

求出第一個交點在哪,所以需要計算每個交點與起點的距離,最近的那個交點就是第一個交點

//y = (-c - a * x )/b

if

b

!=

0f

{

if

xMin

<

x2

&&

xMin

>

x1

{

tempHitPoint

x

=

xMin

var

y

=

-

c

-

a

*

xMin

/

b

if

y

<

yMax

&&

y

>

yMin

{

tempHitPoint

y

=

y

hit

=

true

var

dis

=

tempHitPoint

-

origin

)。

magnitude

if

dis

<

hitDistance

{

hitPoint

=

tempHitPoint

hitDistance

=

dis

}

}

}

}

其他三條邊使用相同的原理。 對應的演示場景在

Test/Scenes/RaycastAABB

總結

這樣,我們就能實現Unity的Raycast類似功能的方法了:

public

static

void

Raycast

QuadTree

quadTree

Vector2

origin

Vector2

direction

ref

JRaycastHitList

hitList

float

distance

int

layMask

{

var

hitCount

=

0

var

ray

=

direction

normalized

*

distance

var

destPoint

=

origin

+

ray

//根據射線起點和終點構造AABB

var

rayRect

=

CreateRect

origin

destPoint

);

//粗測階段,得到可能相交的AABB列表

var

itemList

=

quadTree

GetItems

rayRect

);

//精測階段,同時考慮layMask

foreach

IQuadTreeItem

item

in

itemList

{

var

collider

=

item

selfCollider

var

layer

=

collider

gameObject

layer

if

layMask

Contains

layer

{

continue

}

if

collider

gameObject

activeInHierarchy

{

continue

}

if

collider

enabled

{

continue

}

//計算一條射線與AABB是否相交

CalculateRayHit

collider

origin

direction

ref

hitList

distance

ref

hitCount

);

}

}

這裡還用到我自己寫的一個

layerMask

功能,就是用來

設定哪些層和哪些層需要計算碰撞的功能

。如圖,Default層只和Default、Character、Platform層進行碰撞。指令碼在

Scripts\Editor\JPhysicsSettingInspector

。 使用了

EditorGUILayout.MaskField

的API。

【物理篇】從零搭建2D物理系統③——物體相交測試(完結)

以上就是整個2D物體系統系列文章的全部內容了。從上個月底到現在從專案原始碼和寫部落格花了大概半個多月時間了。因為時間有限只做了最基礎的功能,大家看這些文章主要還是以學習為主,實戰中需要親自搭建物理系統的需求還是少。那麼為什麼我還要寫這系列文章?

為了提高知識的廣度,從而讓自己在未來面試時有更多優勢

。像是物體相交測試的分離軸定理,在米哈遊和位元組的高階客戶端崗位面試時都可能會問到。

當每個人的業務能力差不多的時候,比拼的就是理論知識

。其實我很討厭

內卷

這個詞,不過是競爭激烈換了個說法。在如今越來越內卷的程式設計師行業,只有不斷學習和精進,才能避免自己被淘汰。

說迴文章,從第一篇的框架搭建,到第二篇的空間劃分,到這一篇的相交測試,考慮到受眾和我的時間有限,每一篇的內容其實深度都不是特別深,選擇四叉樹只是因為實現起來比較簡單,同時能複習到關於二叉樹的知識,在我的演示專案裡四叉樹效率並不高,

比unity的射線檢測差了差不多4~5倍左右吧

實戰中需要進一步最佳化四叉樹的查詢演算法或者選用其他更合適的資料結構

。相信大家全部看完後對於物理系統有著更深刻的理解。我是水雞,我們下期再見。

水曜日雞

,簡稱

水雞

,ACG宅。曾參與索尼中國之星專案研發,具有2D聯網多人動作遊戲開發經驗。

CSDN部落格:

https://

blog。csdn。net/j75691537

0

知乎專欄:

https://

zhuanlan。zhihu。com/c_12

41442143220363264

交流學習群:891809847