lecture:design_with_prototyping:紋の再構成

もん(紋)

「もん」は平安時代から伝わる、シンボルマークの一種です。私達の生活では家紋がよく知られているかと思います。みなさんのお家にも家紋があるかと思います。このページはもんをプログラミングで描いてみます。とはいっても、そもそも「もん」がどんなものか、どうやって描いているものなのかを把握しないと興味すらわかないと思いますので、まずは次の動画を御覧ください。

端的に言えば、コンパスと定規で描く図形なわけですが、無限のパターンがここから生み出される様子がわかると思います。制作側がコントロールする描画は

  1. 任意の半径の円を任意の位置で、任意の範囲描画する
  2. 任意の2点を直線で結ぶ

この2つを実行できれば、様々な紋章が描けることがわかります。それではProcessingを利用して、実際に家紋を題材に描画していきたいと思います。

参考:紋の種類はWikipedia家紋一覧をご参照ください。

習作

まずは、予め関数を用意しておきます。もんを描くにはたくさんの線、曲線が必要なので、プログラムの記述をなるべく煩雑にならないようにすることと、モジュール化の練習が目的です。では、下記に関して関数にします。「任意の」というのは変数(パラメータ)を指しています。

  1. 任意の半径の円を任意の位置で、任意の範囲描画する
  2. 任意の2点を直線で結ぶ

で、気づいた方も多いと思いますが、processingでは上記内容は関数ですでに用意されています。

  • syntax
    • arc(a, b, c, d, start, stop)
    • arc(a, b, c, d, start, stop, mode)
  • Parameters
    • a float: x-coordinate of the arc's ellipse
    • b float: y-coordinate of the arc's ellipse
    • c float: width of the arc's ellipse by default
    • d float: height of the arc's ellipse by default
    • start float: angle to start the arc, specified in radians
    • stop float: angle to stop the arc, specified in radians

Processingリファレンスより抜粋 https://processing.org/reference/arc_.html

また、任意の点を直線で結ぶのはline()にてすでに実装済みとなっています。今回はこれらを活用して一緒にデザインを思考してみます。

まずは、様々な家紋を観察してください。どのようにして描いているか(プロセス)を中心に観察します。非常に多くの種類があるので、すべてに通じる共通性を見出すのは困難ですが、ある程度眺めていると渡しの場合は、一つの特徴を発見できました。それは、任意の半径に沿って、任意の個数の円を描く作業がよく見られる。というものです。以下の紋章は十菊と呼ばれる家紋(菊紋)の一つです。これをよく見ると、中心に一つの円、周辺に10個の円を描く作業があることがわかります。また、中心から外に放射状に直線が伸びる描画も、よく家紋に見られる特徴だと思いました。

Japanese Crest Jyuu Kiku

以上の観察から、任意の点を中心に、任意の半径上に円を任意の個数均等(互いに接して)に並べることが可能な描画モジュールを作ることとします。とはいえまずは最初はこちらで作成しました。雛形からスタートしていきます。

void setup()
{
  size(500, 500);
}

/*
Abstract:
_r を中心とした円状に円を並べて描画する。回転描画する円の半径は、_rと_nの値で自動的に他辺と接する値に調整される。

Parameter:
 _x : 基準となる中心座標 x 
 _y : 基準となる中心座標 y
 _r : 基準となる円の半径
 _angle : 基準となる円の傾き(角度)
 _arc_begin : 回転描画する円弧の書き出し位置(角度)
 _arc_length : 回転描画する円弧の長さ(角度)
 _n : 回転描画する円の個数
*/
void drawRevolvingPattern(
  float _x, float _y, float _r, 
  float _angle, float _arc_begin, float _arc_length, 
  int _n)
{
  float _angle_step = 360.0/(float)_n;
  float r_sub = _r*2*cos(radians((180.0-_angle_step)/2.0));  
  noFill();
  for (float i = -90; i < 360-90; i=i+_angle_step) {
    arc(_x+_r*cos(radians(i+_angle)), _y+_r*sin(radians(i+_angle)), 
      r_sub, r_sub, 
      radians(_angle + i+_arc_begin), radians(_angle + i+ _arc_begin + _arc_length));
  }
}

void draw()
{
  background(255);
  strokeWeight(1);
  stroke(0);
  drawRevolvingPattern(width/2, height/2, 150, 0, 0, 360, 10);
}

実行結果

まずは drawRevolvingPatternのアルゴリズムをしっかりと理解してください。ProcessingのTweak機能を使って、動的に draw()関数内にある、drawRevolvingPattern()のパラメータをいじってどれをいじるとどのように変化するのかを合わせて観察します。振る舞いが理解できたら、パラメータを調整して、 菊の輪郭を描いてみます。具体的には次のような画像にしてみましょう。

次はここに放射状に広がる描画モジュールを追加します。先程のモジュールにも少し似ていますが、任意の点を中心に決められた本数の線を中心から外側に向かって均等に放射するものになります。下記の雛形を使ってください。

/*
Abstract: 
 (_x, _y) を中心として、_n 個の直線を均等に放射状に_rの長さだけ描画する。

Parameter
 _x : 基準となる中心座標 x
 _y : 基準となる中心座標 y
 _r : 直線の長さ
 _angle : 放射直線の回転角度
 _n : 放射直線の数
*/
void drawRadialWire(
  float _x, float _y, float _r, 
  float _angle, 
  int _n)
{
  float angle_step = 360/(float)_n;
  for ( float i = -90; i < 360-90; i=i+angle_step ) {
    line(_x, _y, _x+_r*cos(radians(i+_angle)), _y+_r*sin(radians(i+_angle)));
  }
}

ここまでで準備ができましたので、上記の情報を参考にして、菊の紋章を描いてください。

さて、ここまでデザインの観察(デッサン)からスケッチを描いてきたわけですが、「手書きのほうがらくだね」、「別のソフトで描いたほうがもっときれいにできるし、色塗りとかも簡単なんじゃない?」と思う人も少なくないと思います。わざわざモジュール化したのには理由がありまして、プログラミングの一つの面白さとして、パラメータ化が挙げられます。基本となる雛形を用意し、あとはパラメータを調整するだけで基本の雛形からまた別の紋章を表現することが可能になります。実際に先程作成したサンプルスケッチを私が適当に弄ったものを下記に示します。これらをまた一から手書きしたり、グラフィックソフトウェアで描くのは骨がおれますが、パラメトリックに「もん」を対応させることで、思いもしないような紋を発見することもできます。

さらに観察

菊の紋章は比較的単純であったので比較的シンプルにパラメトリックにすることができましたが、紋章には様々なものがあり、それらすべてが習作で実装したコードだけでは対応できないものも多くあります。しかしながら基本となる描画方法自体はコードにできているので、それらをうまく活用することで菊以外も描くことができます。下記画像は NHK デザインあのウェブサイトからのスクリーンショットです。

桜の紋は比較的菊にベースが似ていますが、よく見ると花びらに関して少し細かな装飾が施されています。一見すると直線で引いているようにもみえますが、画像をよく見てもらうとわかるように、大きな円の一部を切り取った線であることがわかります。これら補助線を参考に、先程作ったプログラムで桜の紋章を描いた様子を2分間の早送り動画にまとめました。試行錯誤の様子をご覧ください。既存のdrawRevolvingPatternそのままでは円周上円のサイズは個数で決まってしまっていたので、個別に設定できるようにもう一つ関数を準備しました。

ここまでのコードを下記に示します。

void setup()
{
  size(1200, 1200);
}

/*
Abstract:
 _r を中心とした円状に円を並べて描画する。
 
 Parameter:
 _x : 基準となる中心座標 x 
 _y : 基準となる中心座標 y
 _r : 基準となる円の半径
 _r_sub : 円周上に配置する円の半径(省略した場合は、他辺と接するように自動調整される
 _angle : 基準となる円の傾き(角度)
 _arc_begin : 回転描画する円弧の書き出し位置(角度)
 _arc_length : 回転描画する円弧の長さ(角度)
 _n : 回転描画する円の個数
 */
void drawRevolvingPattern(
  float _x, float _y, float _r, float _r_sub, 
  float _angle, float _arc_begin, float _arc_length, 
  int _n)
{
  float _angle_step = 360.0/(float)_n;
  noFill();
  for (float i = -90; i < 360-90; i=i+_angle_step) {
    stroke(0, 0, 0, 50);
    ellipse(_x+_r*cos(radians(i+_angle)), _y+_r*sin(radians(i+_angle)), _r_sub, _r_sub);
    stroke(0, 0, 0, 255);
    arc(
      _x+_r*cos(radians(i+_angle)), _y+_r*sin(radians(i+_angle)), 
      _r_sub, _r_sub, 
      radians(_angle + i+_arc_begin), radians(_angle + i+ _arc_begin + _arc_length)
      );
  }
}
void drawRevolvingPattern(
  float _x, float _y, float _r, 
  float _angle, float _arc_begin, float _arc_length, 
  int _n)
{
  float _angle_step = 360.0/(float)_n;
  float r_sub = _r*2*cos(radians((180.0-_angle_step)/2.0));  
  drawRevolvingPattern(_x, _y, _r, r_sub, _angle, _arc_begin, _arc_length, _n);
}


/*
Abstract: 
 (_x, _y) を中心として、_n 個の直線を均等に放射状に_rの長さだけ描画する。
 
 Parameter
 _x : 基準となる中心座標 x
 _y : 基準となる中心座標 y
 _r : 直線の長さ
 _angle : 放射直線の回転角度
 _n : 放射直線の数
 */
void drawRadialWire(
  float _x, float _y, float _r, 
  float _angle, 
  int _n)
{
  float angle_step = 360/(float)_n;
  for ( float i = -90; i < 360-90; i=i+angle_step ) {
    line(_x, _y, _x+_r*cos(radians(i+_angle)), _y+_r*sin(radians(i+_angle)));
  }
}

void draw()
{
  background(255);
  strokeWeight(1);
  stroke(0);

  drawRevolvingPattern(width/2, height/2, 100, 130, -5, 35, 90, 5);
  drawRevolvingPattern(width/2, height/2, 100, 130, 5, -125, 89, 5);

  drawRevolvingPattern(width/2, height/2, 360, 508, 29, 194, 8, 5);
  drawRevolvingPattern(width/2, height/2, 360, 508, 44, 158, 8, 5);

  stroke(0, 0, 0, 50);
  drawRadialWire(width/2, height/2, 351, 36, 5);  
  ellipse(width/2, height/2, 320, 320);
  ellipse(width/2, height/2, 90, 90);

  stroke(0, 0, 0, 255);
  drawRadialWire(width/2, height/2, 81, 36, 5);
  drawRadialWire(width/2, height/2, 45, 0, 10);

  fill(255);
  ellipse(width/2, height/2, 40, 40);
}


void keyPressed()
{
  if ( key == 's' ) {
    save("output.png");
  }
}

ここまで実際に紋をプログラムで記述しましたが、

  • arc()関数で、描かない範囲を薄っすらと描いてほしい

と思ったので、arc()を別定義で以下のようにしました。以降はこのarc()関数を利用することにします。

/*
Abstract:
 円弧を描く関数。指定円弧以外の円弧を描くかどうかを選択できる。
 
 Parameter:
 _x : 中心x座標
 _y : 中心y座標
 _r : 半径
 _start : 描き出す角度
 _stop : 描く角度量
 _alpha : 円弧の薄さ
 */
void arc(float _x, float _y, float _r, float _start, float _stop, int _alpha)
{
  _start = _start -90;
  _stop = _stop -90;
  stroke(0, 255);
  arc(_x, _y, _r, _r, radians(_start), radians(_stop));
  stroke(0, _alpha);
  arc(_x, _y, _r, _r, radians(_stop), radians(360+_start));
}

では、紋章「唐花」をプログラムにて記述してください。

Japanese Crest Karahana

Reference

これら紋は円や直線の組み合わせでしたが,そのサイズは一体どのように決めたら良いでしょうか?その一つの指針となるものに黄金比があります.詳細は下記リンクで確認してください.

  • lecture/design_with_prototyping/紋の再構成.txt
  • 最終更新: 2019/11/22 01:16
  • by baba