有关消灭星星的描边算法

popStar 又叫 消灭星星。 闲着想了下算法。 点击星星格子 会出现星星的描边,就是多边形格子的描边 最终 效果类似下图.

中心思想,贴出核心代码  

--[[
    假如 一个格子 上方的格子 是无效格子  
    意味着这个格子的上面的这条边 是多边形的轮廓边 
    同理 根据四个方向的无效点 就可以获取 四个方向的轮廓边
    最终将所有无效边 连接起来就是多边形的轮廓
]]
--[[
    step1:
    @param  单个格子
    @return1 返回 在map内 上下左右的有效格子
    @return2 返回 在map内 无效方向。用来获取多边形的轮廓
   
    step2:
    1、获取所有点
    2、根据点之间的距离 分组
    获取 任意点a  如果 找不到 a 相邻 格子间距 的点就说明是轮廓点
    如果能找到 偶数个点 就丢弃 说明可以合并

    如果奇数个点  就保留 说明是转折点
]] 

处理点击的方法

--@param  点击位置
function GameLayer:handleTouch( pos_tbl_ )
    local pos_x = pos_tbl_.x
    local pos_y = pos_tbl_.y

    -- 根据点击的点 获取所属行列 x y
    local x,y = getXYForPosition({pos_x,pos_y})
    print(tostring(x) .. " === handleTouch === " .. tostring(y))

    if x <= 0 or x > MAP_WIDTH then
        print("Touch x not in map " .. tostring(x))
        return 
    end

    if y <= 0 or y > MAP_HEIGHT then
        print("Touch y not in map " .. tostring(y))
        return 
    end

   -- 根据xy 坐标获取到对应的点
    local target_node = self:getPointWidgetByPos({x, y})
    if target_node then
        -- 根据格子节点 获取 挨着的所有相同颜色的点
        local points = self:getAdJoinPoints({x, y}, target_node.color)
        if #points <= 1 then
            print("只有一个单元结点,不会标记或删除")
            return 
        end

        -- 根据所有 同色格子 获取所有格子的边的向量集合
        local edges = self:getBorderPointsByInvalidDirPoints(points)
        -- 合并所有同方向 首尾相接的向量
        local ret = self:combineEdges(edges)
        -- 将所有向量的边 重新转换成点
        local border_points = self:convertEdges2Position(ret)
        -- 将所有点连线 画出来
        self:drawNodesBorder(border_points, cc.c4f(0.5, 0, 0.5, 1), self._main_panel)
    else
        print("not find target_node index = " .. tostring(index))
    end
end

通过dfs 获取相邻同色节点

--[[
    @func 用来判断当前点是否有效 就是合法的格子 
    因为后面格子会消除  所以宽度高度都是不定的 维护一个二维的array
    
    @param1 当前点
    @param2 当前map所有列的array
    每个array中 对应列的格子的集合
]]
local function isPointValid( temp_pos_tbl_, map_height_tbl_ )
    -- 当前列的最高值
    local cur_line_height = map_height_tbl_[temp_pos_tbl_[1]]
    local cur_line_count = #(map_height_tbl_)
    return (temp_pos_tbl_[1] >= 1 and temp_pos_tbl_[1] <= cur_line_count) and (temp_pos_tbl_[2] >= 1 and temp_pos_tbl_[2] <= cur_line_height)
end

--[[
    dfs 根据单个格子位置 获取所有同色格子的集合
]]
function GameLayer:getAdJoinPoints( pos_tbl_, color_ )

    -- 获取一个点 相对轮廓来说 有效格子集合和无效格子集合
    local function getNearByPoint( temp_pos_tbl_ )
        local temp_x = temp_pos_tbl_[1]
        local temp_y = temp_pos_tbl_[2]

        local temp_ret = {}
        -- 方向向量
        local dir_points = { {0,1}, {0,-1}, {-1,0}, {1,0} }

        local invalid_dir_points = {}
        for i=1,#dir_points do
            local dir_p = dir_points[i]
            local p = {temp_x + dir_p[1], temp_y + dir_p[2], dir_p}
            if isPointValid(p, self._map_height_tbl) then
                table.insert(temp_ret, p)
            else
                table.insert(invalid_dir_points, dir_p)
            end
        end
        return temp_ret, invalid_dir_points
    end

    -- 已经处理过的列表 
    local handled_key_map = {}
    -- 预处理 队列的key map 格子的查重 避免一直遍历
    local pre_point_key_map = {}
    -- 将要处理的列表
    local pre_point_list = {}

    --[[
        起始点 -> 获取邻接有效点 ->放入预处理队列
        遍历预处理队列 -> 处理 -> 处理完成 -> 获取当前结点的邻接有效点
        如果当前结点在预处理队列中 则跳过 不在 则加入预处理队列。
        广度优先
    ]] 
    local ret_tbl = {}
-- 起始点 local t_p = pos_tbl_ repeat -- 放入处理过的格子map local handled_key = getKeyByPos(t_p) handled_key_map[handled_key] = 1 -- 获取邻接的有效格子集合 和 无效格子集合 local nearby_points, invalid_dir_points = getNearByPoint(t_p) -- 遍历有效格子集合 for i,v in ipairs(nearby_points) do local temp_pre_key = getKeyByPos(v) if not pre_point_key_map[temp_pre_key] and not handled_key_map[temp_pre_key] then -- 比较有效格子的颜色 颜色相同的点就是想要的 local node = self:getPointWidgetByPos(v) if node.color == color_ then table.insert(pre_point_list, {v[1], v[2]}) --标记这个点已经处理过 pre_point_key_map[temp_pre_key] = 1 v[3] = {} end end end -- 将无效方向的点 放入结果集中 在下一步需要用 table.insert(t_p, invalid_dir_points) table.insert(ret_tbl, t_p)
t_p
= table.remove(pre_point_list, 1) until (not t_p) return ret_tbl end

获取轮廓边向量

--[[
    @param1:
    points_tbl_ = {
        {
            x,
            y,
            {
                {
                    1,
                    0
                },
                {
                    -1,
                    0
                },
                ...
            }
        },
        ...
    }
    这个方法是用来 将无效点转换成 边的向量
    最终将向量连接起来变成多边形的轮廓

    .. {0,1},{1,1}
    .. {0,0},{1,0}

    四个格子组成一个田字
    左下格子的右上顶点 定义为{0,0}点
    左上格子的右下顶点 定义为{0,1}点
    右上格子的左下顶点 定义为{1,1}点
    右下格子的左上顶点 定义为{1,0}点
    将这个属性放到 边的顶点的第三个位置 组成点 {x,y,dir_p} 下一步用来方便边的合并
]]
function GameLayer:getBorderPointsByInvalidDirPoints( points_tbl_ )

    -- local dir_points = { {0,1}, {0,-1}, {-1,0}, {1,0} }
    -- 返回一个 向量 { {x1,y1, 额外属性dir_p}, ... }

    -- 规定方向向量都是顺时针转 按照顺时针组成一个多边形的轮廓
    local function getEdgeByXYAndDirP( x_, y_, dir_p_ )
        -- 方向向量{0,1}即相对本格子上边的格子为无效格子 则对应的轮廓边为 上面横着的从左到右
        if dir_p_[1] == 0 then
            if dir_p_[2] == 1 then
                return {{x_, y_ + 1, {1,0}}, {x_+1, y_+1, {0,0}}}
            else 
                -- 方向向量{0,0}即相对本格子下边的格子为无效格子 则对应的轮廓边为 下面横着的从右到左
                return {{x_+1, y_, {0,1}}, {x_, y_, {1,1}}}
            end
        elseif dir_p_[2] == 0 then
            if dir_p_[1] == 1 then
                -- 同理 竖着的 右边缘 从上到下
                return {{x_+1, y_+1, {0,0}}, {x_+1, y_, {0,1}}}
            else-- 同理 竖着的 左边缘 从下到上
                return {{x_, y_, {1,1}}, {x_, y_+1, {1,0}}}
            end
        end
    end

    -- 先将格子根据坐标排序 这样获取的边会更集中
    local points_tbl = points_tbl_
    table.sort(points_tbl, function ( v1_, v2_ )
        if v1_[1] == v2_[1] then
            return v1_[2] > v2_[2]
        end
        return v1_[1] > v2_[1]
    end)

    local direct_edges_tbl = {}
    for i,v in ipairs(points_tbl_) do
        local dir_p_tbl = v[3]
        for j,vv in ipairs(dir_p_tbl) do
            local edge = getEdgeByXYAndDirP(v[1], v[2], vv)
            table.insert(direct_edges_tbl, edge)
        end
    end

    return direct_edges_tbl
end

合并边

--[[
    根据交点 把边串起来
    @param1 
    direct_edges_tbl = {
        {{x1_, y1_}, {x2_, y2_}},
        {{x2_, y2_}, {x3_, y3_}},
        ...
    }
]]
function GameLayer:combineEdges( direct_edges_tbl_ )
    -- 将对角线上的两个点 合并成 一个点 针对品字形的左肩这种情况
    local function getPdir( p1_, p2_ )
        local dir_1 = p1_[3]
        local dir_2 = p2_[3]

        -- 两个点x,y存在相同 就直接return 否则返回两个点的延长线交点
        if dir_1[1] ~= dir_2[1] and dir_1[2] ~= dir_2[2] then
        else
            return nil
        end
        
        -- 点1的x摩2取反  点2的y摩2取反 
        local x = (dir_1[2] + 1) % 2
        local y = (dir_2[1] + 1) % 2

        local ret_dir = {x, y}
        return ret_dir
    end

    --核心就是操作 这个深拷贝出来的边的集合 将边连起来
    local copy_direct_edges_tbl = clone(direct_edges_tbl_)
    local function getSamePointsEdge(edge_)
        local t_p = edge_[2]
        for i=1, #copy_direct_edges_tbl do
            local edge = copy_direct_edges_tbl[i]
            local temp_p1 = edge[1]
            local temp_p2 = edge[2]
            local point_value = t_p[3]

            if t_p[1] == temp_p1[1] and t_p[2] == temp_p1[2] then
                edge = table.remove(copy_direct_edges_tbl, i)
                local t = getPdir(t_p, temp_p1)
                if t then
                    t_p[3] = t
                    temp_p1[3] = t
                end
                return  {edge[1], edge[2]}
            end

            if t_p[1] == temp_p2[1] and t_p[2] == temp_p2[2] then
                edge = table.remove(copy_direct_edges_tbl, i)
                local t = getPdir(edge_[1], temp_p1, t_p[3])
                return  {edge[2], edge[1], t}
            end
        end
    end

    -- 从一条边的 起始点p1开始走,从当前边edge的另一个点p2到 copy_direct_edges_tbl 边的集合里面去找另一条边的相同点
    local cur_edge = table.remove(copy_direct_edges_tbl, 1)
    local cur_p = cur_edge[1]
    local order_edges = {v}
    for i,v in ipairs(direct_edges_tbl_) do
        local edge = getSamePointsEdge(cur_edge)
        if edge then
            table.insert(order_edges, edge)
            cur_edge = edge
        else
            --最后一条边 会找不到下一个
            print("getSamePointsEdge not find node ===")
        end
    end

    -- 起始点和尾节点要连起来  这里有点问题  遇到不完全闭合的图形会出现连接位置问题 (问题不大,再优化啦)
    if cur_p and cur_edge then
        local t = getPdir(cur_edge[2], cur_p)
        if t then
            cur_edge[2][3] = t
            cur_p[3] = t
        end
    end

    --合并同一方向上的 边 首尾相接的合并
    local function mergeTwoEdge( edge1_, edge2_ )
        local t_p1 = edge1_[1]
        local t_p2 = edge1_[2]
        local t_p3 = edge2_[1]
        local t_p4 = edge2_[2]

        local point_value1 = t_p1[3]
        local point_value2 = t_p4[3]

        local x = t_p1[1]
        if x == t_p2[1] and x == t_p3[1] and x == t_p4[1] then
            if point_value1[1] == point_value2[1] or 
                point_value1[2] == point_value2[2] then
                    return true, {t_p1, t_p4}
            end
        end

        local y = t_p1[2]
        if y == t_p2[2] and y == t_p3[2] and y == t_p4[2] then
            -- return true, {t_p1, t_p4}
            if point_value1[1] == point_value2[1] or 
                point_value1[2] == point_value2[2] then
                    return true, {t_p1, t_p4}
            end
        end
        return false
    end

    -- 合并同一条直线上的点
    local ret_edges = {}
    local cur_edge = nil
    for i,v in ipairs(order_edges) do
        if cur_edge then
            local flag, edge = mergeTwoEdge(cur_edge, v)
            if flag then
                cur_edge = edge
            else
                table.insert(ret_edges, cur_edge)
                cur_edge = v
            end
        else
            cur_edge = v
        end
    end

    return ret_edges
end

把合并后的边还原成点

--[[
    把边还原成点
]]
function GameLayer:convertEdges2Position( order_edges_ )
    local max_x, max_y = 0, 0

    --根据第三位属性  计算是否添加 格子间距
    local function convertIndexPoint2Pos( p_ )
        local pos = getXY2Position(p_)

        local x = pos[1]
        local y = pos[2]

        local dir_x = p_[3][1]
        local dir_y = p_[3][2]
        if dir_x == 0 then
            x = x - MAP_CELL_SEP_WIDTH
        end

        if dir_y == 0 then
            y = y - MAP_CELL_SEP_WIDTH
        end
        return {x = x, y = y}
    end

    --把点从边上拆出来
    local points = {}
    for i,edge in ipairs(order_edges_) do
        if i == 1 then
            local p = edge[1]
            table.insert(points, p)
        end
        local p = edge[2]
        table.insert(points, p)
    end

    local ret_points = {}
    for i,v in ipairs(points) do
        table.insert(ret_points, convertIndexPoint2Pos(v))
    end
    return ret_points
end

完啦!

最终的结果就是如上图 代码写的有点啰嗦,但是几个月之后,自己还能看懂 还不错哈, 有问题请指教咯

在这里记录下,其实根据这个思想 是可以做多边形的描边算法的. 比如三角形组合起来做描边算法...  

posted @ 2018-08-02 00:43  lesten  阅读(573)  评论(0编辑  收藏  举报