ASi

PolygonとScan Line Conversion

スキャン ライン コンバージョン

Polygonの塗りつぶし、fillPolygonを作る必要があって勉強した。アルゴリズムは Scan Line Conversion と言うのが定番らしい。
下記サイトがソース付きで参考になる。


Cによる。アルゴリズム解説が大変勉強になる。
http://www2.starcat.ne.jp/~fussy/algo/algo6-1.htm
http://www2.starcat.ne.jp/~fussy/algo/algo6-2.htm
http://www2.starcat.ne.jp/~fussy/algo/algo6-3.htm


Javaによる。Appletですぐに動かせる。
http://nis-lab.is.s.u-tokyo.ac.jp/~koiso/applet/ScanConv/index.html


両サイトのコードともアルゴリズムの基本は同じであるが、データ構造の作り方に差がある。
また、どちらのコードにも一点欠陥があると思われる。


Fussy氏のコードでは頂点で検出した交差辺が奇数になることを防ぐため、交差辺検出の終端を1pixel短く見るようにしている。http://www2.starcat.ne.jp/~fussy/algo/algo6-3.htm の冒頭に氏による説明がある。
ところが、こうすると、ポリゴンの下端がY軸方向に1pixel短く描画されてしまう。X軸と平行な下辺を持つポリゴンを描画すると分かりやすい。


Koiso氏のコードは、X軸と平行な辺に対する対処が不足している為、そのような辺を持つデータを与えると描画がされない。


Fussy氏のコードの場合、GenerateEdgeList 及び PolyFill関数中の下記コードの (a) (A) (B) (C) を、後掲のコードで変更することで、厳密な描画を行うよう修正できる。

(オリジナル抜粋 GenerateEdgeList 関数)

    if ( y != 0 ) {
      Edge->X = ( y > 0 ) ? * ( ApexList - 2 ) << 16 : *ApexList << 16; // (a) を挿入

(オリジナル抜粋 PolyFill 関数)

    /* yと終点が一致した「アクティブ」な辺を削除する */
    for ( epp = NonActiveEdgePointer + 1 ; epp <= ActiveEdgePointer ; ) {
      if ( ( *epp )->Y1 == y ) {          // (A) に変更
        *epp = *ActiveEdgePointer;
		*ActiveEdgePointer-- = &eof;
      } else epp++;
    }

    /* 交点のソート */
    InsertSort( NonActiveEdgePointer + 1 , ActiveEdgePointer );
    /* クリッピング・エリア外の交点をチェックしながらライン描画 */
    for ( epp = NonActiveEdgePointer + 1 ; epp < ActiveEdgePointer ; epp += 2 )  	{
      x0 = ((*epp)->X) >> 16;             // (B) に変更
      x1 = ((*(epp+1))->X) >> 16;         // 
      if ( x1 < MINX || x0 > MAXX ) continue;
      if ( x0 < MINX ) x0 = MINX;
      if ( x1 > MAXX ) x1 = MAXX;
      for ( x = x0 ; x <= x1 ; x++ ) pset(x,y,Col);
      /* X座標の更新 */
      (*epp)->X += (*epp)->a;             // (C) に変更
      (*(epp+1))->X += (*(epp+1))->a;     //
    }

(struct EDGBUF)

  short X1;   // メンバを追加

(a)

      Edge->X1 = y > 0 ? *ApexList : *(ApexList - 2);

(PolyFill関数先頭)

    struct EDGBUF* pre = &eof;
    short preX = -1;

(A)

      if ( ( *epp )->Y1 < y ) {

(B)

      struct EDGBUF* e0;
      struct EDGBUF* e1;
      if ((*epp)->X >> 16 == (*(epp + 1))->X >> 16 && (*epp)->Y0 == (*(epp + 1))->Y1){
      	// 交差辺が奇数になる場合、描画済みの辺を外す
      	e0 = (*epp);               //  @ epp+1
      	e1 = (*(epp + 2));         // @ <--
      	epp++;                     //  @ epp 
      }else if (preX == (*epp)->X >> 16 && pre->Y0 == (*epp)->Y1){
      	// 交差辺が奇数になる場合、描画済みの辺を外す
      	epp++;                     //  @ epp
      	e0 = (*epp);               // @ <--
      	e1 = (*(epp + 1));         //  @ pre
      }else if ((*epp)->Y0 == y && (*(epp + 1))->Y0 < y && (*(epp + 1))->Y1 == y){
      	// 閉じた区間が連続する場合、今の区間と次の閉区間を繋げる
      	e0 = (*epp);               // epp+1>@  @         epp+1>@
      	e1 = (*(epp + 1));         // --> @O@  @       ->  @O   @
      	e1->X = (*(epp + 2))->X;   // epp>@    @ epp+2     @    @ epp+2
      }else{
      	e0 = (*epp);
      	e1 = (*(epp + 1));
      }
      x0 = (short)((e0->X) >> 16);
      x1 = (short)((e1->X) >> 16);
      pre = e1;
      preX = x1;

(C)

      if (e0->Y1 == y + 1){
      	e0->X = e0->X1 << 16;
      }else{
      	e0->X += e0->a;
      }
      if (e1->Y1 == y + 1){
      	e1->X = e1->X1 << 16;
      }else{
      	e1->X += e1->a;
      }

いずれにせよ、大変有意義な記事とコードを公開して下さっている両氏に感謝したい。