<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在上篇文章我們討論了使用 髒矩形渲染,通過重渲染區域性的圖形來提優化 Canvas 的效能,將 GPU 密集轉換為 CPU 密集。
看這篇文章《Canvas 效能優化:髒矩形渲染》
CPU 密集在哪?
在需要遍歷 所有的圖形,判斷它們是否和髒矩形發生相交(碰撞),儲存發生碰抓給你的圖形,將它們在區域性進行重繪。
有沒有辦法減少需要遍歷的圖形,不要遍歷全部的圖形,而是少量的圖形呢?有一個辦法是使用 四元樹。
我們將區域的分割表述為 “節點”,因為是四元樹;
將畫布上的真實圖形就叫做 “圖形”。
四元樹本質使用了 空間分割,給圖形加 索引,將視口介面分割成多個區域,每個區域記住自己包含了哪些圖形。
然後移動目標圖形時,判斷它落在哪個區域,取出所在區域的圖形,這些圖形集合就是和目標圖形發生碰撞圖形的超集。
這些區域外的圖形就被我們排除了。
演演算法實現的要點:
建立根節點,根節點儲存區域的資訊 x、y、width 和 height。
新增圖形時,當一個節點內的節點數量大於閥值,就將整個區域均等切割為 4 等份的子節點,將圖形從當前區域取出,重新放入到這些子節點內,從而將節點的歸屬劃分為更小的區域。
(原來的區域轉換為索引層,真正儲存節點的地方放到了它的子區域上)
當我們提供一個碰撞矩形,我們從四元樹頂節點往下找,看是否有子節點。如果有,使用矩形碰撞演演算法找出它所在的子節點有哪些(可能有多個)。對這些子節點重複前面的操作,進行遞迴,找到所有的圖形。
這些圖形就是碰撞矩形可能相交的矩形,但相對所有圖形,又不至於太多。
先看看經典演演算法實現。
演演算法我就不自己實現了,這裡展示 quadtree-js 庫的程式碼實現。
建構函式:
function Quadtree(bounds, max_objects, max_levels, level) { this.max_objects = max_objects || 10; // 節點內最大物件數量 this.max_levels = max_levels || 4; // 樹的最大深度 this.level = level || 0; this.bounds = bounds; // 區域,結構為 {x, y, width, height} this.objects = []; // 儲存圖形的地方 this.nodes = []; // 4 個子節點,到達閥值時才建立 }
這是一個內部私有方法,當節點內圖形過多,超過閥值,就將當前節點分 裂成 4 個子節點:
// 切割:生成 4 個子節點 Quadtree.prototype.split = function () { var nextLevel = this.level + 1, subWidth = this.bounds.width / 2, subHeight = this.bounds.height / 2, x = this.bounds.x, y = this.bounds.y; // 右上 this.nodes[0] = new Quadtree( { x: x + subWidth, y: y, width: subWidth, height: subHeight, }, this.max_objects, this.max_levels, nextLevel ); // 左上 this.nodes[1] = new Quadtree( { x: x, y: y, width: subWidth, height: subHeight, }, this.max_objects, this.max_levels, nextLevel ); // 左下 this.nodes[2] = new Quadtree( { x: x, y: y + subHeight, width: subWidth, height: subHeight, }, this.max_objects, this.max_levels, nextLevel ); // 右下 this.nodes[3] = new Quadtree( { x: x + subWidth, y: y + subHeight, width: subWidth, height: subHeight, }, this.max_objects, this.max_levels, nextLevel ); };
計算某個圖形落在當前節點的哪個子節點,拿到對應節點索引值陣列:
// 找到某個 box 落在在哪個區域 Quadtree.prototype.getIndex = function (pRect) { var indexes = [], verticalMidpoint = this.bounds.x + this.bounds.width / 2, horizontalMidpoint = this.bounds.y + this.bounds.height / 2; var startIsNorth = pRect.y < horizontalMidpoint, startIsWest = pRect.x < verticalMidpoint, endIsEast = pRect.x + pRect.width > verticalMidpoint, endIsSouth = pRect.y + pRect.height > horizontalMidpoint; // top-right quad if (startIsNorth && endIsEast) { indexes.push(0); } // top-left quad if (startIsWest && startIsNorth) { indexes.push(1); } // bottom-left quad if (startIsWest && endIsSouth) { indexes.push(2); } // bottom-right quad if (endIsEast && endIsSouth) { indexes.push(3); } return indexes; };
插入一個圖形,先看是否存在子節點,有的話說明當前節點變成了索引層,通過矩形碰撞演演算法找到所在的子節點,對這些子節點做插入操作:
Quadtree.prototype.insert = function (pRect) { var i = 0, indexes; // 存在子節點,則插入到子節點 if (this.nodes.length) { indexes = this.getIndex(pRect); // 找到索引位置 for (i = 0; i < indexes.length; i++) { this.nodes[indexes[i]].insert(pRect); // 遞迴 insert } return; } // 沒有子節點,不是索引層,圖形放到前節點下 // (有個小 BUG,就是不在範圍內的圖形也加上去了) this.objects.push(pRect); // 如果物件太多,構建子節點 if ( this.objects.length > this.max_objects && this.level < this.max_levels ) { if (!this.nodes.length) { this.split(); } // 將 objects 取出,放入這些子節點中 for (i = 0; i < this.objects.length; i++) { indexes = this.getIndex(this.objects[i]); for (var k = 0; k < indexes.length; k++) { this.nodes[indexes[k]].insert(this.objects[i]); } } this.objects = []; } };
返回目標圖形所在節點下的所有圖形:
// 傳入一個矩形,取出它所在節點的所有矩形 // 這個方法能返回 Quadtree.prototype.retrieve = function (pRect) { // 提取目標矩形所在區間下的所有矩形 var indexes = this.getIndex(pRect), returnObjects = this.objects; // 可能需要遞迴。 //if we have subnodes, retrieve their objects if (this.nodes.length) { for (var i = 0; i < indexes.length; i++) { returnObjects = returnObjects.concat( this.nodes[indexes[i]].retrieve(pRect) ); } } // 移除重複的節點 returnObjects = returnObjects.filter(function (item, index) { return returnObjects.indexOf(item) >= index; }); return returnObjects; };
非常簡單,一些可以改善的能力。
insert(rect, data)
,儲存額外的資訊,比如實際形狀物件或 id。請根據實際需要進行擴充套件。
處於節點內分割線上的圖形,它是歸屬於多個子節點的,所以最終會同時放到它的多個子節點下,會花費記憶體。
極端情況下,一個非常大的圖形,會儲存在所有的節點下。
如果想節省記憶體,可以直接儲存到當前節點上,不放到子節點上,可以減少記憶體使用,只是最後返回的被碰撞圖形會多一點。因為圖形可能只壓在了兩個子節點的交界線上,比如 A、 B ,但目標矩形是在其他的子節點 C 上,但因為它們來自同一個父節點,所以拿到了這個不可能在 C 的圖形。
後者會更好一些,但如果一個圖形剛好在畫布中心,那每次取出的碰撞圖形都會有它(這點可以通過鬆散四元樹解決)。
經典四元樹有個問題,就是如果圖形的物理資訊是比較動態的,當總是在邊界附近時,就會發生頻繁地將圖形從一個節點取出並放到另個節點下。
對此我們可以額外設定一個出口邊界。這個出口邊界要比入口邊界要大,只有當圖形離開這個出口邊界,才會更新提取圖形到新的節點。
這樣,當圖形劃分到另一個節點上時,就 需要移動較長的距離才能回到原來節點下,輕微地移動不會導致劇烈的更新。
通常出口邊界邊長為入口邊界的兩倍最佳,為什麼不知道,經驗之談。
簡單介紹一些也使用了 空間分割 思想的演演算法。
O(logn)
時間複雜度,但也帶來了記憶體的消耗。Redis 中的有序集合(Sorted Set)底層使用了跳錶,一個原因是可以高效地獲取區間內的資料集;R 樹的思路是最接近四元樹的,它其實是另一種 減少圖形遍歷的方案,可以適用於高效剔除視口範圍之外的圖形。
R 樹有個 star 數很多的庫,叫做 RBush,感興趣可以看看。
以上就是JS快速檢索碰撞圖形之四元樹碰撞檢測的詳細內容,更多關於JS四元樹碰撞檢測的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45