2021-06-18

**

画板功能优化

**

遇到的问题

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 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值