曲線の描画

 Processingには曲線を描画するメソッドが2種用意されています。一つはベジェ(Bezier)曲線でイラストレータのペンツールでも利用されている曲線です。他は、Catsmull-Romのスプライン曲線で、連続する点をつなぐ曲線です。


  1. ベジェ曲線

     ベジェはフランス人の名前です。4点 A,B,C,D を指定すると、始点を A、終点を D とし、C、 D 点で制御される曲線を描きます。制御点 C,D と曲線の関連が直感的で利用しやすい曲線です。

  2. 制御多角形とベジェ曲線

     4点 A,B,C,D の多角形を赤色で、4点から生成されるベジェ曲線を黒色で表示します。左から順にA,B,C,D とします。
      size(300, 150);
      noFill();
      stroke(255, 102, 0);
      line(20, 85, 60, 10);
      line(60, 10, 200, 20);
      line(200, 20, 250, 85);
      stroke(0, 0, 0);
      bezier(20, 85, 60, 10, 200, 20, 250, 85);
     実行結果

     C点だけ位置を変えてみます。
    void setup() {
      background(250);
      size(300, 150);
      noFill();
      stroke(255, 102, 0);
      line(20, 85, 60, 10);
      line(60, 10, 180, 130);
      line(180, 130, 250, 85);
      stroke(0, 0, 0);
      bezier(20, 85, 60, 10, 180, 130, 250, 85);
    }
     実行結果

     制御点に引かれるように,曲線の形が変化します。
  3. パラメトリック表現

     ベジェ曲線を定義する前に、直線のパラメトリックな定義を紹介します。2点を接続する直線l(t)は、
         L(t) = P*t + Q*(1-t) t=0.0..1.0
    で表現できます。P,Qは座標(x,y)を示すベクタ(値の組)で、tは0から1までの少数です。
     L(t)はt=0のときQ、t=1のときP、t=1/2のとき中点 (P+Q)/2 となります。この表現をパラメトリックあるいは媒介変数による定義 と呼びます。tの一時式ですから、直線になります。実際にはPは座標ですから、P*tを、P=(Px,Py)として 
      Lx(t) = Px*t + Qx*(1-t) 
      Ly(t) = Py*t + Qy*(1-t)
    と座標毎に計算します。
     このパラメトリック表現を利用すると、t の多項式でベジェ曲線を定義出来ます。
    A,B 点と一点Cを制御点とする t の2次パラメトリック曲線を考えます。媒介変数tの2次式で表現され、3点A,B,Cから以下のように生成します。
     r(t)=A・(1-t)2 + 2・B・t(1-t) + C・t2
    t=0でA点、t=1でC点となります。A,B,Cは実際には、(x,y,z)の各座標値のベクトルとなります。
    tの2次式ですから、ピークを一つ持つ単純な2次曲線になります。この式は二次のベジェ曲線と呼ばれますが、形状が放物線(2次)になるため、複雑な曲線は表現できません。

  4. 4点のベジェ曲線

     4点で制御されるベジェ曲線は、点 A から始まり、B,Cに制御され、D点で終端する曲線です。4点A,B,C,Dを制御点とするベジェ曲線は、次の式で生成されます。A,B,C,Dは位置ベ クトル、tは[0..1]のスカラー(ベクトルでない)で、tを媒介変数とする3次式となっています。
     r(t)=A・(1-t)3 + 3・B・t・(1-t)2 + 3・C・t2・(1-t) + D・t3
    この式は t=0 のとき点A、t=1のとき点Bとなります。tが0から1に変化するとともに、r(t)の値は A>B>C>D の順に移っていきます。3次式ですから一般に二つのピークをもつことができ、この組み合わせで複雑な曲線を表現できます。4次以上のベジェ曲線も定義できますが、制御が複雑になるためか、あまり利用されません。4点のベジェ曲線には次の数学的な性質があります。
     1.始点A、終点D で曲線は辺AC、BD の接線になります。
     2.制御多角形が凸の場合、ベジェ曲線は凸多角形の内部に入ります。
     3.ベジェ曲線な2次の連続性があります。

  5. Bezierクラスの作成

     数式に従ってベジェ曲線を表示するクラスの作成例を紹介します。タブ欄の 右矢印をクリックし、BezierC のタブを作成し、そこに以下のプログラムを作成します。先頭で Point クラスを定義しています(小文字の point は点を表示するメソッドとして定義されています)。引数なしのコンストラクタと引数付き(初期設定可能)のコンストラクタの2種を定義しています。
     Bezierクラスでは、Point の配列 bc[] に制御点の位置を記録します。メソッド poly() は制御点の位置を円で表示します。curve() はベジェ曲線を表示します。注釈にされている bezier() は Processing で用意されているベジェ曲線表示メソッドです。注釈を無効にすると、黄色で同じベジェ曲線を表示します。
    //Point クラスの定義
    class Point{
      int x;
      int y;
      //引数なしのコンストラクター
      Point(){ };
      //引数付きのコンストラクター
      Point(int x, int y){
        //左の x はこのクラスの x、右辺は引数の x
        this.x=x;
        this.y=y;
      }
    }
    
    //ベジェクラスの定義
    class BezierC {
      Point[] bc = new Point[4];
      //コンストラクター
      BezierC(Point [] bc) {
        for(int i=0;i<4;i++){
          this.bc[i]=new Point();
          this.bc[i].x =bc[i].x;
          this.bc[i].y =bc[i].y;
        }
      }
     //4点を円で表示
     void poly(){
       for(int i=0;i<4;i++){
         ellipse(bc[i].x,bc[i].y,5,5);
        } 
     }
      //曲線表示
      void curve() {
        double t;
        Point bp=new Point();
        bp.x=bc[0].x;
        bp.y=bc[0].y;
        Point np=new Point(0,0) ;
        for (t=0.0;t<=1.001;t=t+0.05) {
          np.x=(int)(bc[0].x*(1-t)*(1-t)*(1-t) + 3*bc[1].x*t*(1-t)*(1-t)
            + 3*bc[2].x*t*t*(1-t) + bc[3].x*t*t*t);
          np.y=(int)(bc[0].y*(1-t)*(1-t)*(1-t) + 3*bc[1].y*t*(1-t)*(1-t)
            + 3*bc[2].y*t*t*(1-t) + bc[3].y*t*t*t);
          line(bp.x,bp.y,np.x,np.y);
          bp.x=np.x;
          bp.y=np.y;
        }
        /* stroke(0,255,0);
        bezier(bc[0].x,bc[0].y,bc[1].x,bc[1].y,
           bc[2].x,bc[2].y,bc[3].x,bc[3].y); */
      }
    }

     以下は、BezierCクラスを利用してベジェ曲線を表示します。制御点をマウスでドラッグする機能が組み込まれています。マウスボタンが押されたとき、マウスの近く(15ポイント以内)に制御点があればその番号を fp に記録し、マウスがドラッグされたとき制御点の座標をマウスの位置に変更します。fp が負の時は マウスがドラッグされても座標の位置はしません。
    //ベジェ曲線の表示
    //制御点をマウスで移動可能
    
    Point[] bp = new Point[4];
     BezierC bz;
     int fp;
    void setup() {
      bp[0] = new Point(10, 60);
      bp[1] = new Point(30, 10);
      bp[2] = new Point(80, 15);
      bp[3] = new Point(120, 60);
      bz = new BezierC(bp);
      fp=-1;
      size(150,150);
    }
    
    void draw(){
      background(200);
      noFill();
       stroke(0,0,0);
      bz.poly();
      stroke(255,0,0);
      bz.curve();
    
    }
    //マウスボタンが押された
    void mousePressed(){
      double dx;
      for(int i=0;i<4;i++){
        dx =sqrt( (bz.bc[i].x-mouseX)* (bz.bc[i].x-mouseX)
              + (bz.bc[i].y-mouseY)* (bz.bc[i].x-mouseY));
        if(dx<15){
          fp=i;
          println(fp);
        }
      }
       println(fp);
    }
    //マウスがドラッグされた
    void mouseDragged(){
      if(fp>=0){
        bz.bc[fp].x = mouseX;
        bz.bc[fp].y = mouseY;
      }
    }
    //マウスボタンが離された
    void mouseReleased(){
      fp=-1;
    }
    

     

  6. まとめ

     クラスの作成の例として、自前のベジェ曲線を表示するクラスの作成例を紹介しました。提供されている機能をそのまま利用するだけでは、深い理解ができない場合もあります。