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
完啦!
最终的结果就是如上图 代码写的有点啰嗦,但是几个月之后,自己还能看懂 还不错哈, 有问题请指教咯
在这里记录下,其实根据这个思想 是可以做多边形的描边算法的. 比如三角形组合起来做描边算法...