过多点圆滑曲线Java版-闭合和不闭合两种

发布于:2022-12-23 ⋅ 阅读:(664) ⋅ 点赞:(0)


说明:
path.cubicTo是安卓三阶贝塞尔曲线的路径调用函数,也可以自己画,文章末尾附有n阶贝塞尔曲线的算法实现

一、不闭合实现

效果

在这里插入图片描述

代码实现

mappedPoints点集,sharpenRatio:控制系数,可以控制圆滑程度

 public static void calculate(List<PointF> mappedPoints, float sharpenRatio, Path path) {
        if (mappedPoints.size() < 3) {
            throw new IllegalArgumentException("The size of mappedPoints must not less than 3!");
        }

//        Logger.d(TAG, "sharpenRatio: " + sharpenRatio);

        PointF pMidOfLm = new PointF();
        PointF pMidOfMr = new PointF();

        PointF cache = null;

        for (int i = 0; i <= mappedPoints.size() - 3; i++) {
            PointF pL = mappedPoints.get(i);
            PointF pM = mappedPoints.get(i + 1);
            PointF pR = mappedPoints.get(i + 2);

            pMidOfLm.x = (pL.x + pM.x) / 2.0f;
            pMidOfLm.y = (pL.y + pM.y) / 2.0f;

            pMidOfMr.x = (pM.x + pR.x) / 2.0f;
            pMidOfMr.y = (pM.y + pR.y) / 2.0f;

            float lengthOfLm = (float) Math.hypot(pM.x - pL.x, pM.y - pL.y);
            float lengthOfMr = (float) Math.hypot(pR.x - pM.x, pR.y - pM.y);

            float ratio = (lengthOfLm / (lengthOfLm + lengthOfMr)) * sharpenRatio;
            float oneMinusRatio = (1 - ratio) * sharpenRatio;

//            Logger.d(TAG, "ratio:%f, oneMinusRatio:%f.", ratio, oneMinusRatio);

            float dx = pMidOfLm.x - pMidOfMr.x;
            float dy = pMidOfLm.y - pMidOfMr.y;

            PointF cLeft = new PointF();
            cLeft.x = pM.x + dx * ratio;
            cLeft.y = pM.y + dy * ratio;

            PointF cRight = new PointF();
            cRight.x = pM.x + -dx * oneMinusRatio;
            cRight.y = pM.y + -dy * oneMinusRatio;

            if (i == 0) {
                PointF pMidOfLCLeft = new PointF((pL.x + cLeft.x) / 2.0f, (pL.y + cLeft.y) / 2.0f);
                PointF pMidOfCLeftM = new PointF((cLeft.x + pM.x) / 2.0f, (cLeft.y + pM.y) / 2.0f);

                float length1 = (float) Math.hypot(cLeft.x - pL.x, cLeft.y - pL.y);
                float length2 = (float) Math.hypot(pM.x - cLeft.x, pM.y - cLeft.y);

                ratio = (length1 / (length1 + length2)) * sharpenRatio;
                PointF first = new PointF();
                first.x = cLeft.x + (pMidOfLCLeft.x - pMidOfCLeftM.x) * ratio;
                first.y = cLeft.y + (pMidOfLCLeft.y - pMidOfCLeftM.y) * ratio;

                path.cubicTo(first.x, first.y, cLeft.x, cLeft.y, pM.x, pM.y);
            } else {
                path.cubicTo(cache.x, cache.y, cLeft.x, cLeft.y, pM.x, pM.y);
            }

            cache = cRight;

            if (i == mappedPoints.size() - 3) {
                PointF pMidOfMCRight = new PointF((pM.x + cRight.x) / 2.0f, (pM.y + cRight.y) / 2.0f);
                PointF pMidOfCRightR = new PointF((pR.x + cRight.x) / 2.0f, (pR.y + cRight.y) / 2.0f);

                float length1 = (float) Math.hypot(cRight.x - pM.x, cRight.y - pM.y);
                float length2 = (float) Math.hypot(pR.x - cRight.x, pR.y - cRight.y);
                ratio = (length2 / (length1 + length2)) * sharpenRatio;

                PointF last = new PointF();
                last.x = cRight.x + (pMidOfCRightR.x - pMidOfMCRight.x) * ratio;
                last.y = cRight.y + (pMidOfCRightR.y - pMidOfMCRight.y) * ratio;

                path.cubicTo(cRight.x, cRight.y, last.x, last.y, pR.x, pR.y);
            }

//            Logger.d(TAG, "cLeft:%s, cRight:%s.", cLeft, cRight);
        }
    }

二、闭合实现

调试过程中的效果

原必经点基础加头三个点,形成闭合,为什么加三个。。。。嗯。。。。尝试加思考,才得出来的。。。。

图中白色实心点是原始要经过得点
空心点是我把辅助点打出来看看
如果能去掉这两个红色圈????,就完美了

在这里插入图片描述

最终效果,随便画,都漂亮!!!!:

在这里插入图片描述

代码实现

这里我用kotlin了,和上面java一样,注意点有两个
划重点!!!划重点!!划重点!!
1、原点基础加三个点:
mappedPoints.add(mappedPoints[0])、 mappedPoints.add(mappedPoints[1])、mappedPoints.add(mappedPoints[2])
2、去掉一轮的起点和二轮的末点,也就是Java代码中i=0和i=mappedPoints.size() - 3去掉既可,实际是去掉两个终点处的尖角点-------------------此处是重点,查了好多资料,都实现不了,最后自己多次尝试思考用这个最简单的办法解决了,自己画画就知道了,刚好多余这两条线形成尖角

kotlin代码实现如下

注意:去掉后path得移到这两个位置得添加,不然画偏了
path.moveTo(pM.x, pM.y);path.moveTo(pR.x, pR.y)

   private fun calculatePassCurve(mappedPoints: List<PointF>, sharpenRatio: Float, path: Path, canvas: Canvas) {
        require(mappedPoints.size >= 3) { "The size of mappedPoints must not less than 3!" }
        val pMidOfLm = PointF()
        val pMidOfMr = PointF()
        var cache: PointF? = null
        for (i in 0..mappedPoints.size - 3) {
            val pL = mappedPoints[i]
            val pM = mappedPoints[i + 1]
            val pR = mappedPoints[i + 2]

            pMidOfLm.x = (pL.x + pM.x) / 2.0f
            pMidOfLm.y = (pL.y + pM.y) / 2.0f

            pMidOfMr.x = (pM.x + pR.x) / 2.0f
            pMidOfMr.y = (pM.y + pR.y) / 2.0f

            val lengthOfLm = hypot((pM.x - pL.x).toDouble(), (pM.y - pL.y).toDouble()).toFloat()
            val lengthOfMr = hypot((pR.x - pM.x).toDouble(), (pR.y - pM.y).toDouble()).toFloat()

            var ratio = lengthOfLm / (lengthOfLm + lengthOfMr) * sharpenRatio
            val oneMinusRatio = (1 - ratio) * sharpenRatio

            val dx = pMidOfLm.x - pMidOfMr.x
            val dy = pMidOfLm.y - pMidOfMr.y

            val cLeft = PointF()
            cLeft.x = pM.x + dx * ratio
            cLeft.y = pM.y + dy * ratio

            val cRight = PointF()
            cRight.x = pM.x + -dx * oneMinusRatio
            cRight.y = pM.y + -dy * oneMinusRatio

            if (i == 0) {
                path.moveTo(pM.x, pM.y)
            } else {
                path.cubicTo(cache!!.x, cache.y, cLeft.x, cLeft.y, pM.x, pM.y)
                canvas.drawPath(mPath, mPaint)
            }
            cache = cRight
            if (i == mappedPoints.size - 3) {
                path.moveTo(pR.x, pR.y)
            }
        }
    }

小插曲

我的点是随机的,调试中间出现了个萌萌熊,让苦笑实现不了闭合圆滑的我,莫名喜感
上图,给大家看看
在这里插入图片描述

三、彩蛋篇----n 阶贝塞尔曲线点生成算法

/**
     * deCasteljau算法
     * p(i,j) =  (1-t) * p(i-1,j)  +  t * p(i-1,j+1);
     *
     * @param i 阶数   4
     * @param j 控制点 3
     * @param t 时间
     * @return
     */
    private fun deCasteljauX(i: Int, j: Int, t: Float): Float {
        if (i == 1) {
            return (1 - t) * mControlPoints[j].x + t * mControlPoints[j + 1].x;
        }
        return (1 - t) * deCasteljauX(i - 1, j, t) + t * deCasteljauX(i - 1, j + 1, t);
    }

    /**
     * deCasteljau算法
     *
     * @param i 阶数
     * @param j 第几个点
     * @param t 时间
     * @return
     */
    private fun deCasteljauY(i: Int, j: Int, t: Float): Float {
        if (i == 1) {
            return (1 - t) * mControlPoints[j].y + t * mControlPoints[j + 1].y;
        }
        return (1 - t) * deCasteljauY(i - 1, j, t) + t * deCasteljauY(i - 1, j + 1, t);
    }

   //返回贝塞尔点,可以直接连接这些点画
    fun buildBezierPoints(controlPoints: ArrayList<PointF>): ArrayList<PointF> {
        mControlPoints = controlPoints
        var points = ArrayList<PointF>()
        var tempPoints = ArrayList<PointF>()
        var order = mControlPoints.size - 1 //阶数
        //画的密集度,帧
        var delta = 1.0f / 1000
        var t = 0f
        while (t < 1) {
            t += delta
            // Bezier点集
            var pointF = PointF(deCasteljauX(order, 0, t), deCasteljauY(order, 0, t))
            points.add(pointF)
        }
        return points
    }

网站公告

今日签到

点亮在社区的每一天
去签到