需求场景

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

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. Py修行路 python基础 (十五)面向对象编程 继承 组合 接口和抽象类

    一.前提回忆: 1.类是用来描述某一类的事物,类的对象就是这一类事物中的一个个体.是事物就要有属性,属性分为 1:数据属性:就是变量 2:函数属性:就是函数,在面向对象里通常称为方法 注意:类和对象均 ...

  2. vue-cli中的babel配置文件.babelrc详解

    本文介绍vue-cli脚手架工具根目录的babelrc配置文件 介绍 es6特性浏览器还没有全部支持,但是使用es6是大势所趋,所以babel应运而生,用来将es6代码转换成浏览器能够识别的代码 ba ...

  3. PHP安装使用Zend Opcache扩展

    简介 Zend OPCache 的前身是Zend Optimizer + (Zend O+),于 2013年3月中旬改名为 Opcache.其通过 opcode 缓存和优化提供更快的 PHP 执行过程 ...

  4. VB.NET使用TagLib#读取MP3中的ID3v2标签

    Taglib#是一个为.NET开发的元数据读取类库,为一个开源项目,可以在他们的官网上获取windows版本的源码包或者编译好的类库:http://download.banshee.fm/taglib ...

  5. 主表当中明细表字段的金额计算问题,操作控件是在gridview+aspnetPage

    做这个例子,主要是我在工作当中遇到一个主表的明细表的操作计算问题,也用了不少时间.操作计算的方式是这样的. 这个功能是在.net语言当中实现,操作过程当点击添加行,添加第一行时,当我输入金额的时候,累 ...

  6. Enumeration & Class & Structure

    [Enumeration] 1.当一个枚举值类型已经确定后,可以使用shorter dot syntax来赋予其它值: 2.对一个枚举值switch的时候也可以使用short dot syntax: ...

  7. 04.webservice客户端调用

    不要求所有的元素都理解,真正做开发的时候,有一些必须是要用的. 以后我们做开发的时候服务访问点的集合就一个服务的访问点.服务访问点绑定了具体的一个服务类,绑定的这个东西它本身也是一个元素.往上找,四个 ...

  8. c语言实践 1/1-1/2+1/3-1/4+...

    其实这个题目和上面那个是一样的 /* 1/1-1/2+1/3-1/4+...1/n; */ int n = 1; double sum = 0; double frac = 0; int i = 1; ...

  9. jQuery--基础知识速查表

    一.jQuery选择器 选择器 实例 选取 * $("*") 所有元素 #id $("#lastname") id="lastname" 的 ...

  10. Luogu 4755 Beautiful Pair

    分治 + 主席树. 设$solve(l, r)$表示当前处理到$[l, r]$区间的情况,我们可以找到$[l, r]$中最大的一个数的位置$mid$,然后扫一半区间计算一下这个区间的答案. 注意,这时 ...