ベジェ曲線
解説/アルゴリズム
二次ベジェ曲線
3つの点ABCを用意して、点AB、点BCを直線で結んで2つの線を作る。補間係数の値は何でもいいのだが、とりあえず 0.3 を設定する。
つまり、A ~ B までの距離を 30% 進んだ場所と、B ~ C までの距離を 30% 進んだ場所の位置を求める。(上記の D、E 地点)
線形補間で求めた位置同士を線で結ぶ。
その結んだ線から更に線形補間をする。補間係数の値は上と同じで 0.3。
つまり、D ~ E までの距離を 30%進んだ場所を求める。(上記の F 地点)
この求めた F 地点の位置が二次ベジェ曲線で描かれる曲線の位置になる。
補間係数を 0.0 ~ 1.0 まで求めて繋げて軌跡にすると、二次ベジェ曲線を描くことができる。
三つの点から二本の直線を描き、二つの線形補間の点を求める。
二つの点から一本の直線を描き、一つの線形補間の点を求める。
まとめると二次ベジェ曲線のアルゴリズムは上記の通りとなる。
N個の点からN-1本の直線を描き、N-1個の線形補間の点を求める
(点が一つになるまでループ)
一般化すると、点の数をいくらでも増やし複雑なベジェ曲線を描くことができる。
三次ベジェ曲線
点の数が 4 つの三次ベジェ曲線。
コード例
function drawBezier(a: Point, b: Point, c: Point): void {
let px = a.x;
let py = a.y;
const max = 100;
for (let i = 1; i <= max; i++) {
const t = i / max;
const ab = lerp(a, b, t);
const bc = lerp(b, c, t);
const d = lerp(ab, bc, t);
p.line(px, py, d.x, d.y);
px = d.x;
py = d.y;
}
}
線形補間による二次ベジェ曲線を描くコード例。
ベジェ曲線の特徴
- 与えられた点のうち、最初の点を始点、最後の点を終点、間にある点を制御点と呼ぶ。
- N 個の点からできる曲線は N-1 次曲線となる。
線形補間で一つの点が求まるまで計算するのではなく、一つの式で計算する方法がある。
点 ABC、補間係数 t が与えられている状態で二次ベジェ曲線を描くとする。
- 点 AB 間の補間係数 t による線形補間の式は
- 点 BC 間の補間係数 t による線形補間の式は
この 2 つの式から求まる点から更に線形補間をするので、
が、求めたい点の位置になる。
function calcBezier(a: number, b: number, c: number, t: number): number {
return (a * (1 - t) + b * t) * (1 - t) + (b * (1 - t) + c * t) * t;
}
function drawBezier(a: Point, b: Point, c: Point): void {
let px = a.x;
let py = a.y;
const max = 100;
for (let i = 1; i <= max; i++) {
const t = i / max;
const x = calcBezier(a.x, b.x, c.x, t);
const y = calcBezier(a.y, b.y, c.y, t);
p.line(px, py, x, y);
px = x;
py = y;
}
}
上記で求めた式を利用した二次ベジェ曲線を描くコード例。
三次以上も同じように計算すればいいのだが、かなりややこしい計算になるので、もう少し楽な計算方法を試してみる。
展開した式 | |
---|---|
を二項定理などを利用して展開すると上記のような式になるが、ここで x を 、 y を に置き換える。
次数 | x を、y をに置き換えた場合 |
---|---|
一次 | |
二次 | |
三次 | |
四次 |
この置き換えた式が実際にベジェ曲線で使用される式で、初項 * 点 A, 第 2 項 * 点 B, 第 3 項 * 点 C...と項ごとに位置を掛ければ、補間係数 t における位置が算出できる。
例えば点 ABCD を渡して三次ベジェ曲線の位置を求める場合は、下記の式になる。
function calcBezier3(a: Point, b: Point, c: Point, d: Point, t: number): Point {
const x =
a.x * (1 - t) ** 3 +
b.x * 3 * (1 - t) ** 2 * t +
c.x * 3 * (1 - t) * t ** 2 +
d.x * t ** 3;
const y =
a.y * (1 - t) ** 3 +
b.y * 3 * (1 - t) ** 2 * t +
c.y * 3 * (1 - t) * t ** 2 +
d.y * t ** 3;
return { x, y };
}
function drawBezier3(a: Point, b: Point, c: Point, d: Point): void {
let px = a.x;
let py = a.y;
const max = 100;
for (let i = 1; i <= max; i++) {
const t = i / max;
const point = calcBezier3(a, b, c, d, t);
p.line(px, py, point.x, point.y);
px = point.x;
py = point.y;
}
}