贝塞尔曲线应用实现canvas轨迹运动

canvas提供的方法

1.绘制二次贝塞尔曲线:quadraticCurveTo(cp1x, cp1y, x, y)

ctx.moveTo(10, 200); //起始点
var cp1x = 40, cp1y = 100;  //控制点
var x = 200, y = 200; // 结束点
//绘制二次贝塞尔曲线
ctx.quadraticCurveTo(cp1x, cp1y, x, y);

2.绘制三次贝塞尔曲线:bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)

ctx.moveTo(40, 200); //起始点
var cp1x = 20, cp1y = 100;  //控制点1
var cp2x = 100, cp2y = 120;  //控制点2
var x = 200, y = 200; // 结束点
//绘制二次贝塞尔曲线
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);

贝塞尔曲线的动画演示及公式

1.一次贝塞尔曲线:B(t) = P0(1-t) + tP1,t∈[0,1]
《贝塞尔曲线应用实现canvas轨迹运动》
2.二次bezier公式:B(t) = P0(1-t)² + 2P1 t(1-t) +P2t²,t∈[0,1]
《贝塞尔曲线应用实现canvas轨迹运动》
3.三次bezier公式:B(t) = P0(1-t)³ + 3P1t(1-t)² + 3p2t²(1-t)+P3t³,t∈[0,1]
《贝塞尔曲线应用实现canvas轨迹运动》
4.根据以上得出贝塞尔曲线通用公式,详见贝塞尔公式推导
《贝塞尔曲线应用实现canvas轨迹运动》

利用公式实现canvas轨迹运动

根据前面的公式,我们就能得求出贝塞尔曲线上的点满足的公式,以下就以二次贝塞尔曲线为例说明。

  • 二次贝塞尔曲线的公式是:B(t) = P0(1-t)² + 2P1 t(1-t) +P2t²,t∈[0,1]
  • 所以弧线上的点满足的方程:
x = Math.pow(1-t, 2) * x1 + 2 * t * (1-t) * cx + Math.pow(t, 2) * x2
y = Math.pow(1-t, 2) * y1 + 2 * t * (1-t) * cy + Math.pow(t, 2) * y2
  • 利用公式写一个demo,实现小球沿着贝塞尔曲线运动
    《贝塞尔曲线应用实现canvas轨迹运动》
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    html,
    body,
    canvas { 
      margin: 0;
      padding: 0;
      height: 100%;
      width: 100%;
    }
  </style>
</head>

<body>
  <canvas id="canvas" style="background-color: pink;"></canvas>
</body>
<script>
  const canvas = document.getElementById('canvas')
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
  const ctx = canvas.getContext('2d');
  //起点
  var [x1, y1] = [10, 10];
  //小球起点
  var [x, y] = [10, 10];
  //控制点
  var [cx, cy] = [100, 0];
  //终点
  var [x2, y2] = [200, 200]
  var t = 0

  //画一条线
  function draw() { 
    ctx.beginPath()
    ctx.moveTo(10, 10)
    ctx.quadraticCurveTo(cx, cy, x2, y2)
    ctx.stroke()
    ctx.closePath()
  }

  /*贝塞尔曲线上点位的控制*/
  function computedPosition() { 
    if (y > 200) { 
      t = 0
      x = 0
      y = 0
      return
    }
    x = Math.pow(1 - t, 2) * x1 + 2 * t * (1 - t) * cx + Math.pow(t, 2) * x2
    y = Math.pow(1 - t, 2) * y1 + 2 * t * (1 - t) * cy + Math.pow(t, 2) * y2
  }

  function drawPoint() { 
    computedPosition()
    ctx.beginPath()
    ctx.fillStyle = "red"
    ctx.arc(x, y, 10, 0, 2 * Math.PI)
    ctx.fill()
  }

  let loopDraw = () => { 
    requestAnimationFrame(loopDraw)
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
    draw()
    drawPoint()
    t += 0.007//控制动画移动速度
  }
  loopDraw()//启动动画
</script>
</html>
  • 那么三次的实现只需要修改部分代码
//……
//控制点
  var [cx1, cy1] = [100, 0];
  var [cx2, cy2] = [100, 250]
//……
ctx.bezierCurveTo(cx1, cy1, cx2, cy2, x2, y2)
//……
/*贝塞尔曲线上点位的控制*/
  function computedPosition() { 
    //...
    x = x1 * Math.pow((1 - t), 3) + 3 * cx1 * t * Math.pow((1 - t), 2) + 3 * cx2 * Math.pow(t, 2) * (1 - t) + x2 * Math.pow(t, 3)
    y = y1 * Math.pow((1 - t), 3) + 3 * cy1 * t * Math.pow((1 - t), 2) + 3 * cy2 * Math.pow(t, 2) * (1 - t) + y2 * Math.pow(t, 3)
  }

如何实现绘制n次贝塞尔曲线呢?

通过上面的两个的两个例子我们可以增强对canvas动画实现的理解,即一切变化都基于t(时间)、x,y,z(位置)实现的。以上两个例子只是做个引子,二次贝塞尔曲线多用于加入购物车实现,三次我还没想到具体的应用场景。以上的两个例子只是个引子,我们的最终的目的是要实现轨迹运动,可以随意写入控制点,以上实现二次和三次使用了不同的方式。那实现不同次方的贝塞尔曲线都需要单独写一个方法吗?别急,先来继续搞清楚贝塞尔曲线的公式。

公式理解

贝塞尔曲线通用公式:《贝塞尔曲线应用实现canvas轨迹运动》

可以从具体的例子中找到抽象规律:

  • N = 3: P = (1-t)^2P0 + 2(1-t)tP1 + t^2*P2
  • N = 4: P = (1-t)^3P0 + 3(1-t)^2tP1 + 3(1-t)t^2P2 + t^3*P3
  • N = 5: P = (1-t)^4P0 + 4(1-t)^3tP1 + 6(1-t)2*t2P2 + 4(1-t)t^3P3 + t^4*P4

将贝塞尔曲线一般参数公式中的表达式用如下方式表示:设有常数 a,b 和 c,则该表达式可统一表示为如下形式

a * (1 – t)^b * t^c * Pn

分析当 N 分别为3,4,5 时对应 a,b,c 的值:如 N = 3 时,公式有三个表达式,第一个表达式为 (1-t)^2*P0,其对应 a,b,c 值分别为:1,2,0

N = 3:   1,2,0   2,1,1   1,0,2
a: 1 2 1
b: 2 1 0
c: 0 1 2
N = 4:   1,3,0   3,2,1   3,1,2   1,0,3
a: 1 3 3 1
b: 3 2 1 0
c: 0 1 2 3
N = 5:   1,4,0   4,3,1   6,2,2   4,1,3   1,0,4
a: 1 4 6 4 1
b: 4 3 2 1 0
c: 0 1 2 3 4

根据上面的分析就可以总结出 a,b,c 对应的取值规则:

b: (N – 1) 递减到 0 (b 为 1-t 的幂)
c: 0 递增到 (N – 1) (c 为 t 的幂)
a: 在 N 分别为 1,2,3,4,5 时将其值用如下形式表示:
N=1:———1
N=2:——–1 1
N=3:——1 2 1
N=4:—–1 3 3 1
N=5:—1 4 6 4 1

使用组合实现

//组合
function C(n, i) { 
    return f(n) / f(i) / f(n - i)
}
//阶乘公式 n!
//阶乘 factorial 
function f(n) { 
    if (n < 0) { 
        return -1
    } else if (n === 0 || n === 1) { 
        return 1
    } else { 
        return (n * f(n - 1))
    }
}
    原文作者:七里长亭不负卿
    原文地址: https://blog.csdn.net/qq_45706352/article/details/109855643
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞