zoukankan      html  css  js  c++  java
  • 有关消灭星星的描边算法

    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

    完啦!

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

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

  • 相关阅读:
    Mysql添加用户和数据库
    Ubuntu Apache vhost不执行php小记
    buff/cache内存占用过多
    yii2 返回json和文件下载
    yii2 activeform 替換 form-gruop
    VSCode+Ionic+Apache Ripple开发环境搭建
    安装ionic出现node-sass无法下载的解决方法
    VS2015 + Cordova Html5开发使用Crosswalk Web引擎
    visual studio 2015 + Cordova 开发环境搭建
    ADSL自动更换IP地址源代码
  • 原文地址:https://www.cnblogs.com/lesten/p/9404793.html
Copyright © 2011-2022 走看看