JS實(shí)現(xiàn)的四叉樹算法詳解
本文實(shí)例講述了JS實(shí)現(xiàn)的四叉樹算法。分享給大家供大家參考,具體如下:
最近在看canvas動(dòng)畫方面教程,里面提到了采用四叉樹檢測(cè)碰撞。之前也看到過四叉樹這個(gè)名詞,但是一直不是很懂。于是就又找了一些四叉樹方面的資料看了看,做個(gè)筆記,就算日后忘了,也可以回來(lái)看看。
QuadTree四叉樹顧名思義就是樹狀的數(shù)據(jù)結(jié)構(gòu),其每個(gè)節(jié)點(diǎn)有四個(gè)孩子節(jié)點(diǎn),可將二維平面遞歸分割子區(qū)域。QuadTree常用于空間數(shù)據(jù)庫(kù)索引,3D的椎體可見區(qū)域裁剪,甚至圖片分析處理,我們今天介紹的是QuadTree最常被游戲領(lǐng)域使用到的碰撞檢測(cè)。采用QuadTree算法將大大減少需要測(cè)試碰撞的次數(shù),從而提高游戲刷新性能,
四叉樹很簡(jiǎn)單,就是把一塊2d的區(qū)域,等分成4份,如下圖: 我們把4塊區(qū)域從右上象限開始編號(hào), 逆時(shí)針。
四叉樹起始于單節(jié)點(diǎn)。對(duì)象會(huì)被添加到四叉樹的單節(jié)點(diǎn)上。
當(dāng)更多的對(duì)象被添加到四叉樹里時(shí),它們最終會(huì)被分為四個(gè)子節(jié)點(diǎn)。(我是這么理解的:下面的圖片不是分為四個(gè)區(qū)域嗎,每個(gè)區(qū)域就是一個(gè)孩子或子節(jié)點(diǎn))然后每個(gè)物體根據(jù)他在2D空間的位置而被放入這些子節(jié)點(diǎn)中的一個(gè)里。任何不能正好在一個(gè)節(jié)點(diǎn)區(qū)域內(nèi)的物體會(huì)被放在父節(jié)點(diǎn)。(這點(diǎn)我不是很理解,就這幅圖來(lái)說,那根節(jié)點(diǎn)的子節(jié)點(diǎn)豈不是有五個(gè)節(jié)點(diǎn)了。)
如果有更多的對(duì)象被添加進(jìn)來(lái),那么每個(gè)子節(jié)點(diǎn)要繼續(xù)劃分(成四個(gè)節(jié)點(diǎn))。
正如你看到的,每個(gè)節(jié)點(diǎn)僅包括幾個(gè)物體。這樣我們就可以明白前面所說的規(guī)則,例如,左上角節(jié)點(diǎn)里的物體是不可能和右下角節(jié)點(diǎn)里的物體碰撞的。所以我們也就沒必要運(yùn)行消耗很多資源的碰撞檢測(cè)算法來(lái)檢驗(yàn)他們之間是否會(huì)發(fā)生碰撞。
下面我們對(duì)四叉樹進(jìn)行實(shí)現(xiàn):
主要代碼:(代碼是從整個(gè)四叉樹類里面拷貝出來(lái)的,所以帶有this,大家不要無(wú)視就好,末尾附有完整的代碼)
function QuadTree(boundBox, lvl) { var maxObjects = 10; this.bounds = boundBox || { x: 0, y: 0, width: 0, height: 0 }; var objects = []; this.nodes = []; var level = lvl || 0; var maxLevels = 5; }
maxObjects是每個(gè)節(jié)點(diǎn)能容納的最多對(duì)象超過 則分割4個(gè)節(jié)點(diǎn), 我們并不是事先就分好格子, 而是在插入對(duì)象的時(shí)候才進(jìn)行劃分。
maxLevels是 四叉樹的最大層數(shù) 超過 則不再劃分 從根節(jié)點(diǎn)開始 最多6 層。
level: 當(dāng)前層數(shù)
objects: 當(dāng)前節(jié)點(diǎn)內(nèi)的待檢測(cè)的對(duì)象。
bounds:當(dāng)前節(jié)點(diǎn)所表示的2d區(qū)域的范圍
nodes: 4個(gè)子節(jié)點(diǎn)隊(duì)列。
四叉樹每個(gè)節(jié)點(diǎn)的面積可以為任意形狀。然后,我們會(huì)使用五個(gè)四叉樹里會(huì)用到的方法,分別為:clear,split,getIndex,insert和retrieve。
function clear() { objects = []; for (var i = 0; i < this.nodes.length; i++) { this.nodes[i].clear(); } this.nodes = []; };
Clear函數(shù),是通過循環(huán)來(lái)清除四叉樹所有節(jié)點(diǎn)的所有對(duì)象。
function split() { // Bitwise or [html5rocks] var subWidth = (this.bounds.width / 2) | 0; var subHeight = (this.bounds.height / 2) | 0; this.nodes[0] = new QuadTree({ x: this.bounds.x + subWidth, y: this.bounds.y, width: subWidth, height: subHeight }, level+1); this.nodes[1] = new QuadTree({ x: this.bounds.x, y: this.bounds.y, width: subWidth, height: subHeight }, level+1); this.nodes[2] = new QuadTree({ x: this.bounds.x, y: this.bounds.y + subHeight, width: subWidth, height: subHeight }, level+1); this.nodes[3] = new QuadTree({ x: this.bounds.x + subWidth, y: this.bounds.y + subHeight, width: subWidth, height: subHeight }, level+1); }
Split 方法,就是用來(lái)將節(jié)點(diǎn)分成相等的四份面積,并用新的邊界來(lái)初始化四個(gè)新的子節(jié)點(diǎn)。
function getIndex(obj) { var index = -1; var verticalMidpoint = this.bounds.x + this.bounds.width / 2; var horizontalMidpoint = this.bounds.y + this.bounds.height / 2; // Object can fit completely within the top quadrant var topQuadrant = (obj.y < horizontalMidpoint && obj.y + obj.height < horizontalMidpoint); // Object can fit completely within the bottom quandrant var bottomQuadrant = (obj.y > horizontalMidpoint); // Object can fit completely within the left quadrants if (obj.x < verticalMidpoint && obj.x + obj.width < verticalMidpoint) { if (topQuadrant) { index = 1; } else if (bottomQuadrant) { index = 2; } } // Object can fix completely within the right quandrants else if (obj.x > verticalMidpoint) { if (topQuadrant) { index = 0; } else if (bottomQuadrant) { index = 3; } } return index; };
getIndex 方法是個(gè)四叉樹的輔助方法,在四叉樹里,他決定了一個(gè)節(jié)點(diǎn)的歸屬,通過檢查節(jié)點(diǎn)屬于哪個(gè)象限。(最上面第一幅圖不是逆時(shí)針在一個(gè)面積里劃分了四塊面積,上面標(biāo)示了他們的序號(hào),這個(gè)方法就是算在一個(gè)父節(jié)點(diǎn)里他的子節(jié)點(diǎn)的序號(hào))
比如當(dāng)前區(qū)域是Rectange(0, 0, 600, 600) 待檢測(cè)矩形是Rectangel(0, 0, 30, 30) 那么他就在左上象限 index = 1 如果是Rectange(400, 400, 30, 30) 那么他就在右下象限 index = 3
function insert(obj) { if (typeof obj === "undefined") { return; } if (obj instanceof Array) { for (var i = 0, len = obj.length; i < len; i++) { this.insert(obj[i]); } return; } if (this.nodes.length) { var index = this.getIndex(obj); // Only add the object to a subnode if it can fit completely // within one if (index != -1) { this.nodes[index].insert(obj); return; } } objects.push(obj); // Prevent infinite splitting if (objects.length > maxObjects && level < maxLevels) { if (this.nodes[0] == null) { this.split(); } var i = 0; while (i < objects.length) { var index = this.getIndex(objects[i]); if (index != -1) { this.nodes[index].insert((objects.splice(i,1))[0]); } else { i++; } } } };
每次插入一個(gè)對(duì)象 我們都先看看當(dāng)前節(jié)點(diǎn)有沒有子節(jié)點(diǎn) 如果有 我們就插入子節(jié)點(diǎn)。 一直檢測(cè)到他沒有子節(jié)點(diǎn)為止 我們就把對(duì)象插入到這個(gè)節(jié)點(diǎn), 如果這個(gè)節(jié)點(diǎn)的對(duì)象數(shù)量 > 10個(gè) 并且當(dāng)前節(jié)點(diǎn)的層數(shù) < MAX_LEVELS 我們就把節(jié)點(diǎn)繼續(xù)劃分4個(gè)子節(jié)點(diǎn)。 然后把當(dāng)前對(duì)象循環(huán) 刪除 并插入子節(jié)點(diǎn)。如果對(duì)象在中心線上,getIndex會(huì)返回-1, 所以這些對(duì)象會(huì)被插入到父節(jié)點(diǎn)上面。
一旦對(duì)象添加上后,要看看這個(gè)節(jié)點(diǎn)會(huì)不會(huì)分裂,可以通過檢查對(duì)象被加入節(jié)點(diǎn)后有沒有超過一個(gè)節(jié)點(diǎn)最大容納對(duì)象的數(shù)量。分裂起源于節(jié)點(diǎn)可以插入任何對(duì)象,這個(gè)對(duì)象只要符合子節(jié)點(diǎn)都可以被加入。否則就加入到父節(jié)點(diǎn)。
function retrieve(returnedObjects, obj) { if (typeof obj === "undefined") { console.log("UNDEFINED OBJECT"); return; } var index = this.getIndex(obj); if (index != -1 && this.nodes.length) { this.nodes[index].findObjects(returnedObjects, obj); } for (var i = 0, len = objects.length; i < len; i++) { returnedObjects.push(objects[i]); } return returnedObjects; };
最后一個(gè)四叉樹的方法就是 retrieve 方法,他返回了與指定節(jié)點(diǎn)可能發(fā)生碰撞的所有節(jié)點(diǎn)(就是不停尋找與所給節(jié)點(diǎn)在同樣象限的節(jié)點(diǎn))。這個(gè)方法成倍的減少碰撞檢測(cè)數(shù)量。
四叉樹的代碼就到這里為止了。
完整的代碼如下:
完整的代碼中retrieve就是findObjects。
/** * QuadTree object. * * The quadrant indexes are numbered as below: * | * 1 | 0 * ----+---- * 2 | 3 * | */ function QuadTree(boundBox, lvl) { var maxObjects = 10; this.bounds = boundBox || { x: 0, y: 0, width: 0, height: 0 }; var objects = []; this.nodes = []; var level = lvl || 0; var maxLevels = 5; /* * Clears the quadTree and all nodes of objects */ this.clear = function() { objects = []; for (var i = 0; i < this.nodes.length; i++) { this.nodes[i].clear(); } this.nodes = []; }; /* * Get all objects in the quadTree */ this.getAllObjects = function(returnedObjects) { for (var i = 0; i < this.nodes.length; i++) { this.nodes[i].getAllObjects(returnedObjects); } for (var i = 0, len = objects.length; i < len; i++) { returnedObjects.push(objects[i]); } return returnedObjects; }; /* * Return all objects that the object could collide with */ this.findObjects = function(returnedObjects, obj) { if (typeof obj === "undefined") { console.log("UNDEFINED OBJECT"); return; } var index = this.getIndex(obj); if (index != -1 && this.nodes.length) { this.nodes[index].findObjects(returnedObjects, obj); } for (var i = 0, len = objects.length; i < len; i++) { returnedObjects.push(objects[i]); } return returnedObjects; }; /* * Insert the object into the quadTree. If the tree * excedes the capacity, it will split and add all * objects to their corresponding nodes. */ this.insert = function(obj) { if (typeof obj === "undefined") { return; } if (obj instanceof Array) { for (var i = 0, len = obj.length; i < len; i++) { this.insert(obj[i]); } return; } if (this.nodes.length) { var index = this.getIndex(obj); // Only add the object to a subnode if it can fit completely // within one if (index != -1) { this.nodes[index].insert(obj); return; } } objects.push(obj); // Prevent infinite splitting if (objects.length > maxObjects && level < maxLevels) { if (this.nodes[0] == null) { this.split(); } var i = 0; while (i < objects.length) { var index = this.getIndex(objects[i]); if (index != -1) { this.nodes[index].insert((objects.splice(i,1))[0]); } else { i++; } } } }; /* * Determine which node the object belongs to. -1 means * object cannot completely fit within a node and is part * of the current node */ this.getIndex = function(obj) { var index = -1; var verticalMidpoint = this.bounds.x + this.bounds.width / 2; var horizontalMidpoint = this.bounds.y + this.bounds.height / 2; // Object can fit completely within the top quadrant var topQuadrant = (obj.y < horizontalMidpoint && obj.y + obj.height < horizontalMidpoint); // Object can fit completely within the bottom quandrant var bottomQuadrant = (obj.y > horizontalMidpoint); // Object can fit completely within the left quadrants if (obj.x < verticalMidpoint && obj.x + obj.width < verticalMidpoint) { if (topQuadrant) { index = 1; } else if (bottomQuadrant) { index = 2; } } // Object can fix completely within the right quandrants else if (obj.x > verticalMidpoint) { if (topQuadrant) { index = 0; } else if (bottomQuadrant) { index = 3; } } return index; }; /* * Splits the node into 4 subnodes */ this.split = function() { // Bitwise or [html5rocks] var subWidth = (this.bounds.width / 2) | 0; var subHeight = (this.bounds.height / 2) | 0; this.nodes[0] = new QuadTree({ x: this.bounds.x + subWidth, y: this.bounds.y, width: subWidth, height: subHeight }, level+1); this.nodes[1] = new QuadTree({ x: this.bounds.x, y: this.bounds.y, width: subWidth, height: subHeight }, level+1); this.nodes[2] = new QuadTree({ x: this.bounds.x, y: this.bounds.y + subHeight, width: subWidth, height: subHeight }, level+1); this.nodes[3] = new QuadTree({ x: this.bounds.x + subWidth, y: this.bounds.y + subHeight, width: subWidth, height: subHeight }, level+1); }; }
參考文章:
Quick Tip: Use Quadtrees to Detect Likely Collisions in 2D Space
更多關(guān)于JavaScript相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《JavaScript數(shù)學(xué)運(yùn)算用法總結(jié)》、《JavaScript數(shù)據(jù)結(jié)構(gòu)與算法技巧總結(jié)》、《JavaScript數(shù)組操作技巧總結(jié)》、《JavaScript排序算法總結(jié)》、《JavaScript遍歷算法與技巧總結(jié)》、《JavaScript查找算法技巧總結(jié)》及《JavaScript錯(cuò)誤與調(diào)試技巧總結(jié)》
希望本文所述對(duì)大家JavaScript程序設(shè)計(jì)有所幫助。
- C++實(shí)現(xiàn)四叉樹效果(附源碼下載)
- 四叉樹有損位圖壓縮處理程序示例
- JS實(shí)現(xiàn)的二叉樹算法完整實(shí)例
- JavaScript數(shù)據(jù)結(jié)構(gòu)和算法之二叉樹詳解
- JavaScript實(shí)現(xiàn)多叉樹的遞歸遍歷和非遞歸遍歷算法操作示例
- JavaScript數(shù)據(jù)結(jié)構(gòu)之二叉樹的刪除算法示例
- JavaScript數(shù)據(jù)結(jié)構(gòu)之二叉樹的遍歷算法示例
- JavaScript數(shù)據(jù)結(jié)構(gòu)之二叉樹的查找算法示例
- JavaScript數(shù)據(jù)結(jié)構(gòu)之二叉樹的計(jì)數(shù)算法示例
相關(guān)文章
javascript實(shí)現(xiàn)別踩白塊兒小游戲程序
我們先看到的未開始的頁(yè)面黑色和白色方塊是隨機(jī)生成的,完了這么多把沒有發(fā)現(xiàn)時(shí)固定不變的。點(diǎn)擊一次方塊,程序需要判斷是黑色還是白色的。如果是黑色的,當(dāng)然程序也是實(shí)現(xiàn)了一次自減,在動(dòng)畫中是實(shí)現(xiàn)一次下移的,下移的時(shí)候點(diǎn)擊的方塊到黃顏色的區(qū)域會(huì)變成灰色2015-11-11實(shí)例解析ES6 Proxy使用場(chǎng)景介紹
本篇文章主要介紹了ES6 Proxy使用場(chǎng)景介紹,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧2018-01-01JavaScript輸出所選擇起始與結(jié)束日期的方法
這篇文章主要介紹了JavaScript輸出所選擇起始與結(jié)束日期的方法,涉及javascript結(jié)合HTML5元素操作日期運(yùn)算的相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-07-07Ionic學(xué)習(xí)日記實(shí)現(xiàn)驗(yàn)證碼倒計(jì)時(shí)
本篇文章主要介紹了Ionic學(xué)習(xí)日記實(shí)現(xiàn)驗(yàn)證碼倒計(jì)時(shí),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧2018-02-02file-loader打包圖片文件時(shí)路徑錯(cuò)誤輸出為[object-module]的解決方法
這篇文章主要介紹了file-loader打包圖片文件時(shí)路徑錯(cuò)誤輸出為[object-module]的解決方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01