**
画板功能优化
**
遇到的问题
1: 数据太大,跟服务端通信压力大。
2: 如果关键点距离太远,画出来的线不平滑。
解决方案
1:光栏法压缩关键点的数量。
2:关键点之间用贝塞尔曲线绘制。画出平滑的曲线。
3:压缩关键点通信数据。
光栏法
先来个例子:
例如:在绘制一条直线的时候,取到10个关键点。而我们其实只需要知道头尾2个点就能够绘制了。光栏法的目标就是为了去掉中间那8个点。从而压缩关键点的数量。
下面介绍下光栏法:
光栏法是一种矢量数据的压缩算法。光栏法的基本思路是对曲线上的所有点, 逐点定义一个扇形区域。若曲线的下一节点在扇形外, 则保留当前节点; 若曲线的下一节点在扇形内, 则舍去当前节点。光栏的口径为d(阈值)。
光栏的入口为p1。
连接p1和p2点,过 p2点作一条垂直于p1p2 的直线,在该垂线上取两点a1和a2,使a1p2 = a2p2 = d/2。
p1a1、p1a2 为光栏的两条边界。
p3点在光栏内,舍去p2点。
连接p1和p3,过p3作p1p3的垂线。在垂线上取b1和b2点,使b1p3 = b2p3 = d/2。
缩小光栏。
p1b1与p1p3的夹角 小于 p1a1与p1p3的夹角, p1b1 为新的上边界。
p1b2与p1p3的夹角 大于 p1a2与p1p3的夹角, p1a2 为新的下边界。
p4 不在光栏内,保留p3。
p3 为光栏入口开启新的检测。
通过光栏法能够有效的压缩关键点。但同时会产生一个 “回折点” 问题。
回折点
上图写了一个b。
可以看到关键点的数量被有效的压缩了,但是同时p2到p3之间的线段没了。这是怎么回事呢,我们来看下面这张图:
绘画顺序为P1=》P2=》P3
回折点P3 在“光栏”内。所以P2会被去掉。但其实想保留P2的。否则P2到P3之间的线段将缺失。这不是我们想要的。
所以在遇到回折点的时候应该保留回折点。回折点判断公式为:
P1P2 的长度 - P1P3的长度 > 阈值 d
概化效果
可以看到概化效果还是不错的,但是同时又有一个新的问题,整个图形看起来不够平滑。
这就需要另一个主角,贝塞尔曲线。
贝塞尔曲线绘制
出现该现象的原因主要是:
连接相邻两点的是条直线,非曲线,因此通过这种方式绘制出来的是条折线。两个临近点的距离越远,折线感越明显。
关键点之间可以用二次贝塞尔曲线绘制:
取前面三点A,B,C。计算B,C的中点B1。以A为起点,B为控制点,B1为终点绘制二次贝塞尔曲线
接下来,计算得出C与D点的中点C1,以B1为起点、C为控制点、C1为终点继续绘制曲线。
大角优化
由于贝塞尔曲线的特性。曲线没有到达P2导致失真。
P1P2向量与P2P3向量的夹角大于75度,就把它当做大角。
目标:让曲线经过P2。
P1P2上找到P0。P0为 P2 + P2P1 的单位向量。即P2在P2P1直线上的临近点。
P0为绘制的时候才添加的点,不是实际点。不记录到通信数据中。
最终效果
核心代码(cocos2dx-lua)
– 光栏法概化折线
– @param points
– @param caliber 口径
function simplifyLightBar(srcPoints, caliber)
if caliber <= 0 then
return srcPoints
end
if #srcPoints < 2 then
return srcPoints
end
--点是否在光栏内
-- @param p1 光栏起始点
-- @param p
local isInLightBar = function(up, down, p1, p)
local line = cc.pSub(p, p1)
--叉积判断
if cc.pCross(line, up) >= 0 and cc.pCross(line, down) <= 0 then
return true
end
return false
end
--获取光栏上下向量
local getLightBarVector = function(p1, p2, caliber)
local len = cc.pGetDistance(p1, p2)
local angle = math.atan2(caliber * 0.5, len)
local up = cc.pRotate(cc.pSub(p2, p1),cc.pForAngle(angle))
local down = cc.pRotate(cc.pSub(p2, p1),cc.pForAngle(-angle))
return up, down
end
local acosVector = function(v1, v2)
return math.acos(cc.pDot(v1, v2) / (cc.pGetLength(v1) * cc.pGetLength(v2)))
end
local points = {}
for i =1, #srcPoints do
local p = cc.p(srcPoints[i].x, srcPoints[i].y)
p.enable = true
table.insert(points, p)
end
local p1 = points[1]
local p2 = points[2]
local up, down = getLightBarVector(p1, p2, caliber)
local lastIndex = 1
for i = 3, #points do
local p = points[i]
if isInLightBar(up, down, p1, p) then
--如果下一个点在光栏内,则删除上一个点,当前点为新p2
points[i - 1].enable = false
p2 = points[i]
local newUp, newDown = getLightBarVector(p1, p2, caliber)
--缩小光栏口径
local p1p2 = cc.pSub(p2, p1)
--缩小光栏口径
if acosVector(p1p2, newDown) < acosVector(p1p2, down) then
down = newDown
end
if acosVector(p1p2, newUp) < acosVector(p1p2, up) then
up = newUp
end
else
--如果不在,则保留上一个点,以上一个点为新p1
points[i - 1].enable = true
p1 = points[i - 1]
p2 = points[i]
up, down = getLightBarVector(p1, p2, caliber)
--上一个有效点到现在有效点直线, 之间如果有回折,那最末端的点应该保留
local lastPoint = points[lastIndex]
local maxDis = 0
local maxDisIndex = 0
for j = lastIndex , i - 1 do
local dis = cc.pGetDistance(lastPoint, points[j])
if dis > maxDis then
maxDis = dis
maxDisIndex = j
end
end
if maxDis - cc.pGetDistance(lastPoint, p1) > caliber then
print('===========>>>>> add')
points[maxDisIndex].enable = true
end
lastIndex = i - 1
end
end
local newPoints = {}
for i = 1, #points do
local p = points[i]
if p.enable == true then
table.insert(newPoints, cc.p(p.x, p.y))
end
end
return newPoints
end
–Bezier绘画时,在大的拐角附近添加点,使曲线保持拐角特性
function optimizeBigAngle(points)
if #points < 3 then return points end
local p1, p2, p3
local v1, v2
local angle
local newPoints = {}
for i = 1, #points - 2 do
p1, p2, p3 = points[i], points[i + 1], points[i + 2]
v1, v2 = cc.pSub(p2, p1), cc.pSub(p3, p2)
table.insert(newPoints, p1)
angle = math.acos(cc.pDot(v1, v2) / (cc.pGetLength(v1) * cc.pGetLength(v2)))
--print('=====>>> angle ', angle)
if math.abs(angle) >= math.pi / (180 / 75) then
--print('============>>>> bezier add')
--大拐角添加的点
local p = cc.pAdd(p2, cc.pNormalize(cc.pSub(p1, p2)))
p.isAdd = true
table.insert(newPoints, p)
end
end
table.insert(newPoints, p2)
table.insert(newPoints, p3)
return newPoints
end