如何连接多条贝塞尔曲线?

Posted

技术标签:

【中文标题】如何连接多条贝塞尔曲线?【英文标题】:How to connect multiple bezier curves? 【发布时间】:2020-06-23 05:03:58 【问题描述】:

我想使用bezierCurveTo() 绘制图表。据我所知,只能用1个bezierCurveTo()设置3个锚点。如果我使用其中的多个,我会得到一条不流畅的线路。我该如何解决这个问题?

<canvas id="myCanvas"   style="border:1px solid #d3d3d3;"></canvas>

<script>
  var canvas = document.getElementById('myCanvas');
  var context = canvas.getContext('2d');

  context.beginPath();
  context.moveTo(0, 150);
  context.bezierCurveTo(100, 0, 200, 100, 300, 20);
  context.bezierCurveTo(400, 0, 500, 100, 600, 20);
  
  context.strokeStyle = 'blue';
  context.stroke();
</script> 

【问题讨论】:

命令是“curveTo”,它会自动从你已经在的任何点开始,所以你只需要通过使新曲线的控制点在同一条线上来保证C1的连续性上一条曲线的控制点和最后一点。通读pomax.github.io/bezierinfo/#polybezier 以获取更多信息(以及可能稍后会出现的问题的答案) 【参考方案1】:

这是一个不平凡的问题。这取决于您想要达到多少平滑度(只需连接切线,或使连接点中的曲线半径相同)。最简单的方法如图所示 ([A3-X] / [X-B2] = [A3-A4] / [B1-B2];从 A4 开始 [A3-X] 向量,从 B1 开始 [X-B2] 到获得 A3x 和 B2x 锚点)。

但您也可以查看D3 Shape module(例如 Catmul Rom 曲线),它会从它应该经过的点生成贝塞尔样条曲线。或者在某处查看算法。

【讨论】:

这实际上是一个完全微不足道的问题:给定曲线B1B2,您需要做的就是将B2.c1 设置为a * B1.e - B1.c2,其中a 是一些任意的非零正数常数,e 是曲线上的终点。这使您获得 C1 连续性;设置a=1,现在你甚至有了C2连续性。 @Mike'Pomax'Kamermans 不,这不是微不足道的,有很多用例需要不同的算法。例如。一个用于绘图应用程序(我为 Inker 图形编辑器的算法花了几年时间),另一个用于道路建设(欧拉螺旋),另一个用于图表(这是我的 Taucharts 库算法github.com/TargetProcess/tauCharts/blob/master/src/utils/path/…)。 为各种应用程序选择适合上下文的a 可能并非易事,但这不是我们要回答的问题。做被问到的事情肯定是微不足道的,任务纯粹是确保第二条曲线的前两个坐标与第一条曲线的后两个坐标对齐。【参考方案2】:

我们可以将所有曲线放在一个数组中,然后循环遍历它们,在绘制下一条贝塞尔曲线之前移动到最后一点。下面是一个示例代码:

<canvas id="myCanvas"  ></canvas>
<script>
    var canvas = document.getElementById('myCanvas');
    var context = canvas.getContext('2d');

    function drawCurve(x, y, curves) 
        context.beginPath();
        context.moveTo(x, y);
        for (i = 0; i < curves.length; i++) 
            c = curves[i]
            context.bezierCurveTo(c[0], c[1], c[2], c[3], c[4], c[5]);
            context.moveTo(c[4], c[5]);
            context.stroke();
        
    

    context.strokeStyle = 'blue';
    drawCurve(0, 150, [
        [100, 0, 200, 100, 300, 50],
        [400, 0, 500, 100, 600, 20]
    ]);

    context.strokeStyle = 'red';
    drawCurve(0, 10, [
        [100, 0, 180, 90, 280, 50],
        [400, 0, 400, 80, 600, 120]
    ]);
    
    context.strokeStyle = 'green';
    drawCurve(0, 80, [
        [100, 0, 90, 45, 140, 25],
        [200, 0, 200, 40, 300, 50],
        [500, 60, 400, 80, 300, 120],
        [300, 120, 200, 160, 100, 80],
    ]);
</script>

但是“不平滑的线”也取决于你的曲线,如果它们在完全相反的方向上,我们会看到一个锋利的边缘。

请看下面的例子,我正在画一颗星星。

<canvas id="myCanvas"  ></canvas>
<script>
  var canvas = document.getElementById('myCanvas');
  var context = canvas.getContext('2d');

  function drawCurve(x, y, curves) 
    context.moveTo(x, y);
    for (i = 0; i < curves.length; i++) 
      c = curves[i]
      context.bezierCurveTo(c[0], c[1], c[2], c[3], c[4], c[5]);
      context.moveTo(c[4], c[5]);
    
    context.stroke();
  

  data = []
  numPoints = 12
  size = 35
  angle = 45
  for (j = 0; j < numPoints; j++) 
    a = angle * Math.PI / 180
    points = []
    points.push(80 + Math.round(size / 2 * Math.sin(a)))
    points.push(80 + Math.round(size / 2 * Math.cos(a)))
    points.push(80 + Math.round(size * Math.sin(a)))
    points.push(80 + Math.round(size * Math.cos(a)))
    points.push(80 + Math.round(size * 2 * Math.sin(a)))
    points.push(80 + Math.round(size * 2 * Math.cos(a)))

    angle += 360 / numPoints
    data.push(points)
  
  drawCurve(80, 80, data);
</script>

【讨论】:

以上是关于如何连接多条贝塞尔曲线?的主要内容,如果未能解决你的问题,请参考以下文章

WPF将点列连接成光滑曲线——贝塞尔曲线

如何使用贝塞尔曲线沿路径为图像设置动画

模拟贝塞尔曲线

如何计算三次贝塞尔曲线的长度

使用贝塞尔曲线插值方法对绘图进行圆滑处理

有没有一种简单的方法可以连接两条重叠的贝塞尔曲线的切线?