数学中公式是很重要的,我们先看公式。
公式
线性公式
给定点P0、P1,线性贝兹曲线只是一条两点之间的直线。这条线由下式给出:
B(t)=P0+(P1-P0)t=(1-t)P0+tP1, t ∈[0, 1]
且其等同于线性插值。
二次方公式
二次方贝兹曲线的路径由给定点P0、P1、P2的函数B(t)追踪:
TrueType字型就运用了以贝兹样条组成的二次贝兹曲线。
三次方公式
P0、P1、P2、P3四个点在平面或在三维空间中定义了三次方贝兹曲线。曲线起始于P0走向P1,并从P2的方向来到P3。一般不会经过P1或P2;这两个点只是在那里提供方向资讯。P0和P1之间的间距,决定了曲线在转而趋进P3之前,走向P2方向的“长度有多长”。
曲线的参数形式为:
现代的成象系统,如PostScript、Asymptote和Metafont,运用了以贝兹样条组成的三次贝兹曲线,用来描绘曲线轮廓。
一般参数公式
阶贝兹曲线可如下推断。给定点P0、P1、…、Pn,其贝兹曲线即:
如上公式可如下递归表达: 用表示由点P0、P1、…、Pn所决定的贝兹曲线。
用平常话来说,阶的贝兹曲线,即双阶贝兹曲线之间的插值。
公式说明
1.开始于P0并结束于Pn的曲线,即所谓的端点插值法属性。
2.曲线是直线的充分必要条件是所有的控制点都位在曲线上。同样的,贝塞尔曲线是直线的充分必要条件是控制点共线。
3.曲线的起始点(结束点)相切于贝塞尔多边形的第一节(最后一节)。
4.一条曲线可在任意点切割成两条或任意多条子曲线,每一条子曲线仍是贝塞尔曲线。
5.一些看似简单的曲线(如圆)无法以贝塞尔曲线精确的描述,或分段成贝塞尔曲线(虽然当每个内部控制点对单位圆上的外部控制点水平或垂直的的距离为时,分成四段的贝兹曲线,可以小于千分之一的最大半径误差近似于圆)。
6.位于固定偏移量的曲线(来自给定的贝塞尔曲线),又称作偏移曲线(假平行于原来的曲线,如两条铁轨之间的偏移)无法以贝兹曲线精确的形成(某些琐屑实例除外)。无论如何,现存的启发法通常可为实际用途中给出近似值。
转换矩阵
好,我们一般使用的贝塞尔曲线应该是三阶的。
如上公式,这个公式可以写成矩阵(线性代数自己看,图形编程中常用)的形式。
其中,t 等同于公式3中的t,是一个自变量,而 P 代表一个点,0-3代表4个点,其上的 x, y 分别代表 坐标。所以给定四个点的坐标,t 变换的时候,会得到无数个点坐标,由此点坐标绘制,即成 贝塞尔曲线。
js代码参考:
obite-bezier.js
var matrix = function(config) {
this._init(config)
};
matrix.prototype = {
_init: function(config) {
if (config && config.data)
this.data = config.data;
},
/**
* 矩阵相乘
* @param {matrix} m 被乘的矩阵
*/
mul: function(m) {
var r = [],
s = this.data,
m = m.data,
p = s[0].length //每次运算相加的次数
if (m.length != s[0].length) {
T.trace("矩阵不能相乘")
}
for (var i = 0; i < s.length; i++) {
r[ i ] = []
for (var n = 0; n < m[0].length; n++) {
r[ i ][n] = 0;
for (var l = 0; l < p; l++) {
r[ i ][n] += s[ i ][l] * m[l][n];
}
}
}
this.data = r;
return this;
},
set: function(data) {
this.data = data;
},
get: function() {
return this.data
},
toString: function() {
return this.data.to_s()
}
};
var cubicBezier = function(config) {
this._init(config)
};
cubicBezier.prototype = {
_init: function(config) {
var p = this.points = config.points;
this.m1 = new obite.matrix();
this.m2 = new obite.matrix({
data: [
[1, 0, 0, 0],
[-3, 3, 0, 0],
[3, -6, 3, 0],
[-1, 3, -3, 1]
]
});
this.m3 = new matrix({
data: [
p.p0.toArray(),
p.p1.toArray(),
p.p2.toArray(),
p.p3.toArray()
]
});
this.m = null
},
/**
* 获取某个时间点计算出来的坐标值,时间线不由此类控制
*/
get: function(t) {
/*this.m1.set([
[1, t * t, t * t * t, t * t * t * t]
]);*/
this.m1.set([
[1, t, t * t, t * t * t]
]);
this.m = this.m1.mul(this.m2).mul(this.m3)
return new obite.vector(this.m.get()[0][0], this.m.get()[0][1]);
}
};
var vector = function(x, y) {
this._init(x, y);
};
vector.prototype = {
_init: function(x, y) {
this.x = x;
this.y = y;
},
toArray: function() {
return [this.x, this.y];
}
};
function drawPoint2d(canvas, vector) {
if (typeof canvas === 'string') {
canvas = document.getElementById(canvas);
}
var canvasContext = canvas.getContext('2d');
//在点P处画一个点
with(canvasContext) {
beginPath();
lineWidth = 0.5;
moveTo(vector.x, vector.y);
lineTo(vector.x + 1, vector.y + 1);
strokeStyle = "green";
stroke();
}
}
var obite = function() {};
obite.extend = function(json) {
for (var i in json) {
obite[ i ] = json[ i ];
}
};
obite.extend({
matrix: matrix,
cubicBezier: cubicBezier,
vector: vector,
drawPoint2d: drawPoint2d
});
window.o = obite;
points.js
var points = {
p0: new o.vector(100, 10),
p1: new o.vector(1000, 300),
p2: new o.vector(500, 33),
p3: new o.vector(5, 400)
};
var cb = new o.cubicBezier({
points: points
});
var p;
var t = 0;
var inter = setInterval(function() {
t += 0.001;
if (t > 1) {
end()
}
p = cb.get(t)
//根据p的坐标画点
o.drawPoint2d(canvas,p);
}, 10);
function end() {
clearInterval(inter);
}
bezier.html
<!doctype html>
<html lang="zh">
<head>
<script type="text/javascript" src="obite-bezier.js"></script>
</head>
<body>
<canvas id="c" height="600" width="800"></canvas>
</body>
<script type="text/javascript">
var canvas = document.getElementById("c");
</script>
<script type="text/javascript" src="points.js"></script>
</html>
参考:1. 百度百科:http://baike.baidu.com/view/60154.htm
2. http://bbs.9ria.com/thread-71296-1-1.html