ベジェ曲線を理解する

ベジェ曲線を理解する

その前に線分を式にする。端点(p0, p1)とする。

L(t)=(1-t)P_0 + tP_1,  0 < t < 1

この式は実際の線分の長さはどうであれ 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));
}

postd.cc

曲線を理解する

上の記事にあるSVGを丸パクリしたが、この図を見てベジェ曲線を理解する
点(p0)から点(p1)まで t で変化する点(pt0) と、
点(p1)から点(p2)まで t で変化する点(pt1) を
結ぶ直線を、これまた t で求めた点(Pt2)をなぞると2次ベジェ曲線となる


P 0 P 1 P 2 P t0 P t1 P t2

上の図をもとにプログラムを書く

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次ベジェ曲線となる。


P 0 P 1 P 2 P 3 P t2 P t3 P t4

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>


x(t)=(1-t)^3 \times x0+ 3(1-t)^2 t \times x1+3(1-t) t^2 \times x2+t^3 \times x3\\
y(t)=(1-t)^3 \times y0+ 3(1-t)^2 t \times y1+3(1-t) t^2 \times y2+t^3 \times y3\\
0 \leqq t \leqq 1

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]]
*/