ベジェ曲線を理解する
ベジェ曲線を理解する
その前に線分を式にする。端点(p0, p1)とする。
この式は実際の線分の長さはどうであれ t が0から1の範囲で線分上の任意の点を表せるようにするという意味である。
これをJavaScript で関数にすると以下のようになる。t の変化は 0.01 単位。
function line (p0, p1) { let rst = [ ]; for (let t = 0; t <= 1; t += 0.01) { let pt = (1-t) * p0 + t * p1; rst.push (pt); } return rst; }
これは関数の中で変数 t を変化させている。この t を関数の外から指定できるようにする
function line (p0, p1) { return function (t) { return (1-t) * p0 + t * p1; }; } //__ let p0 = 0, p1 = 1, rst = [ ], p = line (p0, p1); for (let t = 0; t <= 1; t += 0.01) { rst.push (p (t)); }
実際には平面上で直線を扱う場合、x座標とy座標の2つの次元が必要になるので
この関数の引数 a と b を配列にして n 次元の計算ができるように拡張する
function line ([...a], [...b]) { return (t) => { let u = 1 - t; return a.map ((a,i) => u * a + b[i] * t); }; } //__ let p0 = [0], p1 = [1], rst = [ ], p = line (p0, p1); for (let t = 0; t <= 1; t += 0.01) { rst.push (p (t)); }
曲線を理解する
上の記事にあるSVGを丸パクリしたが、この図を見てベジェ曲線を理解する
点(p0)から点(p1)まで t で変化する点(pt0) と、
点(p1)から点(p2)まで t で変化する点(pt1) を
結ぶ直線を、これまた t で求めた点(Pt2)をなぞると2次ベジェ曲線となる
上の図をもとにプログラムを書く
const line =([...a],[...b])=>(t,u)=>(u=1-t,a.map((a,i)=>u*a+b[i]*t)); //__ let p0 = [0, 0], p1 = [550, 100], p2 = [550, 0]; rst = [ ], L01 = line (p0, p1), L12 = line (p1, p2); for (let t = 0; t <= 1; t += 0.01) { let pt0 = L01 (t), pt1 = L12 (t), pt2 = line (pt0, pt1) (t); rst.push (pt2); }
3次のベジェ曲線
まず点(p0)、点(p1)、そして点(p2)をもとにベジェ曲線を描く(赤い点線)。
次に点(p1)、点(p2)、新たに追加した点(p3)をもとにベジェ曲線を描く(緑の点線)。
赤い点線上の点(pt2)と緑の点線上の点(pt3)と結ぶ直線(緑の線)を点(pt4)をとする
その点(pt4)の軌跡が、3次ベジェ曲線となる。
const line =([...a],[...b])=>(t,u)=>(u=1-t,a.map((a,i)=>u*a+b[i]*t)); //__ let [p0, p1, p2, p3] = [[50, 50], [50, 150], [550, 50], [550,150]], rst = [ ], L01 = line (p0, p1), L12 = line (p1, p2), L23 = line (p2, p3); for (let t = 0; t <= 1; t += 0.01) { let pt01 = L01 (t), pt12 = L12 (t), pt23 = L23 (t), pt2 = line (pt01, pt12) (t), pt3 = line (pt12, pt23) (t), pt4 = line (pt2, pt3) (t); rst.push (pt4); }
乗算と減算で3次ベジェ曲線を描く
<!DOCTYPE html> <html lang="en"> <meta charset="UTF-8"> <title></title> <style> canvas { border: 1px solid silver; width: 800px; height: 800px; } </style> <body> <canvas> </canvas> <script> class C { constructor (canvas = document.querySelector ('canvas'), w = canvas.clientWidth, h = canvas.clientHeight) { console.log(canvas); this.canvas = canvas; this.ctx = canvas.getContext('2d'); canvas.width = w; canvas.height = h; } setPoint (x = 0, y = 0, c = 'red') { this.ctx.fillStyle = c; this.ctx.fillRect(x, y, 1, 1); return this; } } function p (p0 = 0, p1 = 0, t0 = 0, t1 = 1 - t0) { return t1 * p0 + t0 * p1; } const c = new C (); c.setPoint (100,100); let P = [[10,0],[20,100],[290,0],[300,100]]; for (let i = 0; i < 100; i++) { let t0 = i / 100, t1 = 1 - t0; let x0 = p(P[0][0],P[1][0], t0, t1), y0 = p(P[0][1],P[1][1], t0, t1); let x1 = p(P[1][0],P[2][0], t0, t1), y1 = p(P[1][1],P[2][1], t0, t1); let x2 = p(P[2][0],P[3][0], t0, t1), y2 = p(P[2][1],P[3][1], t0, t1); let x3 = p(x0, x1, t0, t1), y3 = p(y0, y1, t0, t1); let x4 = p(x1, x2, t0, t1), y4 = p(y1, y2, t0, t1); let x5 = p(x3, x4, t0, t1), y5 = p(y3, y4, t0, t1); c.setPoint (x0, y0, 'red'); c.setPoint (x1, y1, 'red'); c.setPoint (x2, y2, 'red'); c.setPoint (x3, y3, 'blue'); c.setPoint (x4, y4, 'blue'); c.setPoint (x5, y5, 'green'); } </script>
const BZ3b = (a,b,c,d)=>(e=0,f=1-e,g=e*f*3,h=e**3)=>[g*(f*a+e*c)+h,g*(f*b+e*d)+h]; /* linear: [[0.00,0.00],[1.00,1.00]] ease: [[0.25,0.10],[0.25,1.00]] ease-in: [[0.42,0.00],[1.00,1.00]] ease-out: [[0.00,0.00],[0.58,1.00]] ease-in-out:[[0.42,0.00],[0.58,1.00]] */