從零開始學習導航網格#6 recast代碼分析之solomesh(中上)

上回說到Step1 Step2已經將場景模型轉化為帶有可行走信息的體素集span)

Step 3. Filter walkables surfaces.

這一步主要是根據walkableClimb和walkableHeight這兩個參數對span的可行走性做一些修正
將一些原本是不可走面,但可以通過其他可行走的地方爬上去的span標記為可行走(比如樓梯臺階垂直面對應的span)
將一些原本是可行走面,但span高度不夠的標記為不可行走(比如床底下)

Step 4. Partition walkable surface to simple regions.

這一步應該是生成導航網格中最復雜也最難理解的一步,需要做很多處理

a.將高度域轉化為緊縮高度域
緊縮空間

高度場中的span是三角面的體素集,是“實心”的部分
緊縮空間是將實心區域之間的“空心”部分取出來
每個緊縮空間的起始位置是一個實心空間的可行走頂面,高度是“空心”區域的連續高度
找出所有緊縮空間后,再計算出每個緊縮空間與周圍相鄰的緊縮空間的聯通情況
兩個緊縮空間的聯通條件是這樣的:
1.兩個底面的高度差小于可爬行高度
2.高底面與低頂面的高度差大于玩家模型高度

相鄰判斷條件

b.用玩家模型半徑修剪可行走區域邊界(把靠近阻擋或者區域邊界的可行走區域標記為不可行走)

dist[]數組表示每個span(以后的span都特指CompactSpan)到邊界或者阻擋的距離(單位是格子)
1.先找到不可行走的span和邊界span(可行走但鄰居數<4),把它們的dist標記為0
2.從左上角開始向右下角遍歷每個格子的span,用上半方4個格子更新dist
3.從右下角開始向左上角遍歷每個格子的span,用下半方4個格子更新dist
4.將dist小于直徑的span都標記為不可行走


修剪邊界
c.對高度場進行區域劃分

有多種種算法可以選擇,由于服務器是離線生成導航網格,所以我們只看速度最慢但效果最好的分水嶺算法
(如果有需要請參看分水嶺算法的說明
1.創建距離域
先計算出每個span到區域邊界的距離(這一步和b中找區域邊界時求dist[]是一樣的),距離越遠表示越接近區域的中心,距離越小表示越接近區域邊界
再用boxblur來做一次模糊處理(即把span實際的dist修改為周圍九宮格內的span的dist的平均值),使得span的距離值比較平滑
用顏色的深淺來表示距離邊界的遠近,則處理之后的結果如圖所示

距離域

2.根據距離域劃分區域

bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf,
    const int borderSize, const int minRegionArea, const int mergeRegionArea)

這個函數是分水嶺算法的實現部分,作者在編碼時做了很多內存上的優化,所以有些地方寫的比較晦澀,把我看自閉了 ( ? ? ? )
算法的主流程其實并不復雜:
由于距離域中已經記錄了每個span到邊界的距離,距離越遠表示越接近區域的中心
那么將span按距離排序(這里用的是類似于基數排序,把距離相同的span都放到同一個容器中),然后按距離從大到小分批處理(可以看成從每個區域的中心開始擴展填充的過程)
對于當前批次的span,首先看能否加入之前已經存在的區域(與一個標記過的span相鄰),對于不能加入已有區域的span,做一次泛洪填充(調用floodRegion函數)
floodRegion函數做的事情:
以一個span作為起始點,按4鄰域泛洪填充它所能擴展到的區域
遍歷搜索的方式采用深度優先(dfs),而dfs的實現則使用了手動壓棧的非遞歸方式
而每次處理新的節點時,會先判斷它的8個鄰接節點是否已經有了更早的填充標記,如果有,則說明當前節點處于區域相交處,不擴展該節點(該節點會在下一層的expandRegions時被處理)

最后說下代碼中讓人費解的數據結構

const int LOG_NB_STACKS = 3;
const int NB_STACKS = 1 << LOG_NB_STACKS;
rcTempVector<LevelStackEntry> lvlStacks[NB_STACKS];

這是一個層數為8的棧,然后棧中的每個元素又是一個棧

rcTempVector<LevelStackEntry> stack;
stack.reserve(256);

這是一個初始大小為256的棧
這兩個東西其實沒什么關系。。
lvlStacks[NB_STACKS]這個東西是用來做基數排序,然后存放不同距離的span用的,其實沒用到什么棧的特性
至于為什么lvlStacks[]要定義8層。。。我看下來只是作者的一種處于內存考慮的優化寫法,一次性最多處理8層,多余8層下一輪再處理,沒有什么數學上的含義
stack這個東西是在floodRegion時用來手動壓棧實現非遞歸dfs的

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。