本文主要比较三种算法:
1.普通遍历
2.栅格算法
3.四叉树算法
普通遍历
直接上代码
function CollisionScene:checkAllCollision( objs )
local count = 0
for i=1,#objs do
-- k 取 i+1 时间复杂度从n^2降到 n!
for k=i+1,#objs do
-- 设置dirty 减少碰撞检查次数--仅用于一些不是必须所有节点都跟其他节点检查的情形
if not objs[i].dirty and not objs[k].dirty then
count = count + 1
-- 相应碰撞检测方法 aabb等
local isCollistion = self:checkCollision(objs[i],objs[k])
if isCollistion then
objs[i].dirty = true
objs[k].dirty = true
end
end
end
end
return count
end
栅格检测
栅格算法就是划分检测区域为若干块
主要是:
1.创建格子对象grid
-- 初始化格子
function CollisionScene:initGrids( )
self._grids = {} -- 一维数组
self._col = 4
self._row = 4
self._gridW = CONFIG_SCREEN_WIDTH/self._col
self._gridH = CONFIG_SCREEN_HEIGHT/self._row
end
2.将碰撞对象合集objs里每个对象映射到某个格子上(存在一个对象跨多个格子)
-- 填充格子
function CollisionScene:fillGrids( objs )
for k,v in pairs(objs) do
local x = v:getPositionX()
local y = v:getPositionY()
local gridX = math.floor(x/self._gridW)
local gridY = math.floor(y/self._gridH)
local gridId = gridY*self._col+gridX
if not self._grids[gridId] then self._grids[gridId] = {} end
table.insert(self._grids[gridId],v)
local gridXC = math.ceil(x/self._gridW)
local gridYC = math.ceil(y/self._gridH)
-- 跨界处理 并不完全
if gridXC ~= gridX or gridYC ~= gridY then
gridId = gridYC*self._col+gridXC
if not self._grids[gridId] then self._grids[gridId] = {} end
table.insert(self._grids[gridId],v)
end
end
end
3.遍历这些格子对应的 对象集合
function CollisionScene:checkCollistionByGrids( )
local count = 0
self:clearGrids()
self:fillGrids(self._objs)
for _,grids in pairs(self._grids) do
for i=1,#grids do
for k=i+1,#grids do
if i ~= k then
local isCollistion = self:checkCollision(grids[i],grids[k])
count = count + 1
if isCollistion then
grids[i].dirty = true
grids[k].dirty = true
end
end
end
end
end
end
栅格法也是分而治之的算法思想,让时间复杂度从n^2或n! 变成 (n/m)^2或(n/m)! m为格子数。
优化方案之一是生成格子存入map表,如果格子不包含任何检测对象,遍历时跳过–>这个也引出了更进一步的优化算法,四叉树算法
四叉树算法
四叉树算法就是按当前区域的节点数来生成格子,比如当前区域超过4个就进行新格子生成
四叉树算法过程如下
1.生成树基本结构quadTree节点,设置初始节点的区域为 检测区域,最深层 ,分裂条件(对象数超过某值)
function QuardTree:ctor( param )
param = param or {}
self._level = param.level or 1
self._maxLevel = param.maxLevel or 4
self._maxObjCount = param.maxObjCount or 4
self._rect = param.rect or {0,0,0,0}
self._objList = {}
-- 用数组存四个子节点,另一种做法是 四个对象 self._ll self._lr self._ul self._ur
self._children = {}
end
2.填入对象objs
function QuardTree:insertObjs( objs )
self._objList = objs
end
3.根据对象数目,当前层级来判断是否进行分裂
if #self._objList > 4 then
self:split()
end
分裂的方式就是添加子节点
-- 分裂
function QuardTree:split()
if #self._objList <= self._maxObjCount or self._level > self._maxLevel then return end
local rect = self._rect
local x,y = rect[1],rect[2]
local w1,h1 = rect[3]/2,rect[4]/2
local x1,y1 = x+w1,y+h1
-- 四个子节点 四分 当前节点的区域
local childrenRects = {
{x,y,w1,h1},
{x,y1,w1,h1},
{x1,y,w1,h1},
{x1,y1,w1,h1},
}
for i=1,4 do
self._children[i] = QuardTree.new({level = self._level+1,maxObjCount = self._maxObjCount,maxLevel=self._maxLevel,rect=childrenRects[i]})
end
-- 分发当前节点的对象到子节点
self:distributeObjs()
end
4.分发当前节点的对象到子节点
-- 分配节点到相应的quadTree
function QuardTree:distributeObjs( )
for k,v in pairs(self._objList) do
local rect = v:getCascadeBoundingBox()
for i,child in ipairs(self._children) do
local rectL = child:getRect()
if objHitRect(rect,rectL) then
child:insertObj(v)
end
end
end
for i,child in ipairs(self._children) do
child:split()
end
end
5.遍历四叉树,然后拿到按区域划分的对象集合列表
-- 遍历四叉树 中序
function QuardTree:walkTree( walkFunc )
if type(walkFunc) ~= "function" then print("walkFunc is not function") return end
walkFunc(self)
if next(self._children) then
for i=1,4 do
self._children[i]:walkTree(walkFunc)
end
end
end
-- 获得 整个 树的 检测列表
function QuardTree:getRectObjs( )
local objs = {}
self:walkTree(function( quardNode )
-- 没有清除当前节点的对象列表,所以如果有子节点,就略过
if not next(quardNode:getChildrenTree()) then
table.insert(objs,quardNode:getObjList())
end
end)
return objs
end
6.进行常规检测
四叉树算法时间复杂度取决于:
对象总数为n 当前格子的最大对象数目m, 非空格子数为 n/m
n/m*m^2
或 n/m*m!
几近于n*m吗取合适值时可以近似为n
实际比较
节点树少时,栅格和四叉树算法相近
节点多时,