2D碰撞检测算法比较:栅格和四叉树

本文主要比较三种算法:

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

实际比较

节点树少时,栅格和四叉树算法相近
这里写图片描述
节点多时,
这里写图片描述

猜你喜欢

转载自blog.csdn.net/hookby/article/details/68203975