需求场景

一系列的坐标点,划出一条平滑的曲线

3次Bezier曲线

基本上大部分绘图工具都实现了3次Bezier曲线,4个点确定一条3次Bezier曲线。以html5中的canvas为例

let ctx = canvas.getContex('2d');
ctx.moveTo(20,20); // 曲线起点 Fom
ctx.bezierCurveTo(20,100,200,100,200,20); // 分别为控制点 Ctrl1,Ctrl2, 终点 To

连续Bezier曲线

假定给定点的序列List,我们应该以List中的每个点为起点,其下一个点Next为终点绘制Bezier曲线。

所以问题变成,如何确定这两个点之间的两个Bezier控制点。

每一小段路径From-To的Bezier曲线并不是独立的,其实收到了其前后两个点的影响(Prev,Next)

我们在绘制每一段路径的时候,引入其前点Prev,和后点Next共同计算当前Bezier曲线的控制点Ctrl1,Ctrl2

如图所示

  1. 绘制从From到To的Bezier曲线,引入Prev,Next作参考点。
  2. 先依次连线4个点,记为线段l1,l2,l3,并求出其中点c1,c2,c3
  3. 连接中点,在c1c2上找一点f1, 使 l1:l2 = c1f1:f1c2。也就是 c1f1 = c1c2 * l1/(l1+l2)。我叫它线段比例法。看到有些算法是求其垂足,或者中点,尝试之后发现这个线段比例点绘制的曲线最流畅
  4. 将线段 f1c2 平移到 起始点 From上,另一个端点就是所求的控制点Ctrl1。
  5. 通常我们会设置一个0-1的平滑度,乘以要平移的线段f1c2,然后得出最终的控制点。如果平滑度为0,那么其控制点就变成From本身,我们所画出来的图形就是折线。
  6. 同理求出终点To的控制点Ctrl2,计算过程注意平移位置关系。

我们留意到,当绘制第一段或者最后一段曲线时,没有其前后参考点。这里我的做法是如果该点没有Prev,Prev等于自身;如果没有Next,令Next等于To。仅供参考

简单代码示例

Ps. 这段代码并不能直接运行,仅仅是帮助理解,其中大部分点用向量表示,并省略了向量的实现细节。(如果只是想Ctrl+v的程序员,希望你从来没看过这边文章)


/**
* 获取线段AB的k比例点,默认为1/2中点
* @param {*} a
* @param {*} b
* @param {*} k
*/
getCenterPoint: function(a, b, k=0.5) {
return a.add(b.sub(a).mul(k)); // 向量加减乘法,下同。a+(b-a)*k => 点a平移ab的k倍距离
}, /**
* 获取以c点为起点,以向量ab的平移的终点
* @param {*} a
* @param {*} b
* @param {*} c
*/
getTransionPoint: function(a, b, c) {
return c.add(b.sub(a).mul(this.smooth)); // 平移并乘以平滑度
}, /**
* 计算Bezier控制点
* @param {*} from
* @param {*} to
* @param {*} prev
* @param {*} next
*/
getBezierControlPoint: function(from, to, prev, next) {
let p1 = this.getCenterPoint(prev, from);
let p2 = this.getCenterPoint(from, to);
let p3 = this.getCenterPoint(to, next); let f1,f2;
// 使用垂足,不理想
// f1 = this.getFootPoint(p1, p2, from, f1);
// f2 = this.getFootPoint(p2, p3, to, f2); // 使用中点,不理想
// f1 = this.getCenterPoint(p1, p2);
// f2 = this.getCenterPoint(p2, p3); // 使用中点距离的比例点
let len1 = prev.sub(from).mag(); // mag()计算向量prev-from的距离
let len2 = from.sub(to).mag();
let len3 = to.sub(next).mag();
f1 = this.getCenterPoint(p1, p2, len1/(len1+len2)); // p1到p2的k倍距离点
f2 = this.getCenterPoint(p2, p3, len2/(len2+len3)); // 基于比例点作平移得到控制点 [Ctrl1, Ctrl2]
return [this.getTransionPoint(f1, p2, from), this.getTransionPoint(f2, p2, to)];
},

判断某一点是否在我们的连续Bezier曲线上

换句话说,判断鼠标当前位置是否选中某一段Bezier曲线

  1. 如果序列点List的X有序(或者Y有序),(常见的例子是绘制图表,X坐标轴是有序排列的),那么我们先依次对比所有的序列点X坐标,确定其唯一所在区间
  2. 否则,我们要对每一小段Bezier曲线进行判断
  3. 判断Bezier上的一点,我们需要理解Bezier曲线的原理和其函数。我们把这段曲线看作是一条路径,假设从起点走到终点,需要花费10000个单位时间。对于每个单位之间t,我们可以用函数公式求得其坐标:

  1. 我们拿这10000个坐标点与我们的目标点比较,当其差值在一个可接受的范围内时,我们认为目标点就在我们的Bezier曲线上。
  2. 应当注意的是,除了设置的误差范围外,分割的时间片(上面的10000)也会影响到最终结果。如果分割的时间片太少,导致间隙过大使得判断失效。如果分割的时间片太多,又将严重提高计算所消耗的时间。
  3. 示例代码
    /**
* 判断目标点P是否在Bezier曲线(p1,p2,p3,p4)上
* @param {起点} p1
* @param {控制点1} p2
* @param {控制点2} p3
* @param {终点} p4
* @param {待判断点} p
* @param {步长} step
* @param {误差} range
* @return Bezier曲线的选中点
*/
isBezierPoint: function(p1, p2, p3, p4, p, step=0.001, range=0.5) {
for (let t = 0; t <= 1; t += step) {
let x = p1.x*Math.pow(1-t, 3) + 3*p2.x*t*Math.pow(1-t, 2) + 3*p3.x*Math.pow(t, 2)*(1-t) + p4.x*Math.pow(t, 3);
let y = p1.y*Math.pow(1-t, 3) + 3*p2.y*t*Math.pow(1-t, 2) + 3*p3.y*Math.pow(t, 2)*(1-t) + p4.y*Math.pow(t, 3);
if (Math.abs(x - p.x) < range && Math.abs(y - p.y) < range) {
return {x,y};
}
}
return null;
}

Bezier控制点的更新

特别注意一点:

假如我们的序列中某一点的位置发生改变,或者新增了一个序列点,那么其前后将有4个点(忽略端点)的控制点需要重新计算和更新,分别为:

  1. 以这个点为From的曲线(自身为起点)
  2. 以这个点为To的曲线(上一个点为起点)
  3. 以这个点为Prev的曲线(下一个点为起点)
  4. 以这个点为Next的曲线(上一个点的上一个点为起点)
for (let i=currentIndex-2, j=0; j<4; i++,j++) {
// 更新控制点位置
updateBezierControlPoint(i);
}

以上

连续bezier曲线的实现的更多相关文章

  1. Bezier曲线的原理 及 二次Bezier曲线的实现

    原文地址:http://blog.csdn.net/jimi36/article/details/7792103 Bezier曲线的原理 Bezier曲线是应用于二维图形的曲线.曲线由顶点和控制点组成 ...

  2. [摘抄] Bezier曲线、B样条和NURBS

    Bezier曲线.B样条和NURBS,NURBS是Non-Uniform Rational B-Splines的缩写,都是根据控制点来生成曲线的,那么他们有什么区别了?简单来说,就是: Bezier曲 ...

  3. C# 实现Bezier曲线(vs2008)

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  4. 7.5.5编程实例-Bezier曲线曲面绘制

    (a)Bezier曲线                         (b) Bezier曲面 1. 绘制Bezier曲线 #include <GL/glut.h> GLfloat ct ...

  5. 实验6 Bezier曲线生成

    1.实验目的: 了解曲线的生成原理,掌握几种常见的曲线生成算法,利用VC+OpenGL实现Bezier曲线生成算法. 2.实验内容: (1) 结合示范代码了解曲线生成原理与算法实现,尤其是Bezier ...

  6. 简单而粗暴的方法画任意阶数Bezier曲线

    简单而粗暴的方法画任意阶数Bezier曲线 虽然说是任意阶数,但是嘞,算法原理是可以到任意阶数,计算机大概到100多阶就会溢出了 Bezier曲线介绍] [本文代码] 背景 在windows的Open ...

  7. python bezier 曲线

    1.手写bezier公式,生成bezier代码, 如果给的点数过多,则会生成一半bezier曲线,剩下的一半就需要进行拼接: import numpy as np import matplotlib. ...

  8. iOS Bezier曲线

    https://www.jianshu.com/p/2316f0d9db65 1. Bezier曲线 相关软件:PaintCode:可以直接画图,软件根据图像生产Bezier曲线 相关概念:UIBez ...

  9. Bezier曲线的实现——de Casteljau算法

    这学期同时上了计算机图形学和计算方法两门课,学到这部分的时候突然觉得de Casteljau递推算法特别像牛顿插值,尤其递推计算步骤很像牛顿差商表. 一开始用伯恩斯坦多项式计算Bezier曲线的时候, ...

随机推荐

  1. node.js的了解

    在node环境上面运行js代码,js相当于php,node相当于apache环境 第一步装 node 环境1.从官网下载 dmg 文件安装 2.通过命令行安装 需要用到 homebrew(mac上专门 ...

  2. AJAX的流程是什么?

    客户端产生js的事件 创建XMLHttpRequest对象 对XMLHttpRequest进行配置 通过AJAX引擎发送异步请求 服务器端接受请求并且处理请求,返回html或者xml内容 XML调用一 ...

  3. 12个有趣的c语言面试题&nbsp;

    1.gets()函数 问:请找出下面代码里的问题: #include int main(void) { char buff[10]; memset(buff,0,sizeof(buff)); gets ...

  4. 病症:arm启动后应用程序界面显示…

    病症:病症:arm启动后应用程序界面显示不正常(左面有部分未能正常显示)也就是左面少一块区域,右面多一部, 原因:lcd显示驱动中场扫描的问题 平台:s3c2416.linux2.6.800*480l ...

  5. 【原】Zookeeper 概述 + 官网 Overview 翻译

    分布式应用 分布式应用 distributed application可以在给定时间(同时)在网络中的多个系统上运行,通过协调它们以快速有效的方式完成特定任务. (a), (b): a distrib ...

  6. 设置VMware Player中的虚拟机和宿主机共享文件

    设置VMware Player中的虚拟机和宿主机共享文件 试验环境: 虚拟机软件:VMware Player 6.0.3 宿主机os:windows7 虚拟机os:centos6.6(32位)   完 ...

  7. Bootstrap 简介(Web前端CSS框架)

    目录1.简介2.特点3.组件4.Javascript插件5.定制自己的框架代码6.Bootstrap Less 1.简介Bootstrap是Twitter推出的一个开源的用于前端开发的工具包.它由Tw ...

  8. iPython notebook 安装使用

    pip install jupyter jupyter notebook --allow-root

  9. 带你剖析WebGis的世界奥秘----Geojson数据加载(高级)(转)

    带你剖析WebGis的世界奥秘----Geojson数据加载(高级) 转:https://zxhtom.oschina.io/zxh/20160819.html  编程  java  2016/08/ ...

  10. [原创]SQL 把表中字段存储的逗号隔开内容转换成列表形式

    我们日常开发中,不管是表设计问题抑或是其他什么原因,或多或少都会遇到一张表中有一个字段存储的内容是用逗号隔开的列表. 具体效果如下图: ------> 从左边图转换成右边图,像这种需求,我们难免 ...