5:変換と関数 Creative Coding p5.js

アリソンパリッシュによる

このチュートリアルでは、変換の使用方法を見ていきます。変換は、シェイプを画面のどこにどのようにして描画するかを変更するための便利で洗練されたテクニックです。また、関数を記述する基本も見ていきます。関数はコードを区分けして単純化するための方法です。

変換

以前のサンプル画面にシェイプを描画する際、”offset”変数の概念を使用して描画する位置を変えました。たとえば次のスケッチは幼稚な顔を表示しますが、その顔の位置は、xoffsetとyoffset変数を調整することで変更できます。


これはこれで問題はないのですが、xoffsetとyoffsetが数回記述され、繰り返しが多いように思えます。

プログラマーが同じことを何度も何度も入力しなければならない状況をどれだけ嫌うか、これまでのチュートリアルを通してみなさんは十分にご存知のはずです。プログラマーは、これに対処するためほかの場合と同じようにして、この繰り返しを”くくり出す”方法、translate()関数を考案しました。

移動

translate()の詳細に入る前に、実際の動作を見ておくと良いかもしれません。次のスケッチは前と同じものをtranslate()で書き直したものです。


ここでは何がどうなっているのでしょう? 簡単に言うと、translate()関数は座標システムの原点を変更します。translate()の呼び出し以降に呼び出すすべての描画関数で、画面上の0, 0の位置は、translate()の呼び出しで指定した座標になります。

translate()命令の主な利点は、translate()の使用時には、シェイプの描画に記述するコードが位置取りと無関係になる、ということです。シェイプを描画するコードはほかのスケッチにコピー&ペーストでき、その場合前もってtranslate()を使って位置を変更する限り、コードを変更する必要はありません。

次のスケッチは、顔がマウスを追従する例です。このバージョンは、描画関数にmouseXとmouseYを追加しなくても、translate()関数を使用するだけで動作します。


translate()は累積する

しっかりと覚えておかなくてはいけないことがあります。たとえば、「顔を2つ描こう。1つはマウスに追従し、1つは画面左上隅のまま動かなくしよう」と考えたとします。すると次のようなスケッチになるでしょう。


しかしこれではだめです。2つともマウスについて来ます。なぜこうなるのでしょう?

それは、変換は累積するからです。1回めのtranslate()への呼び出しで原点はマウス位置に移動します。2回めの呼び出しは原点を50, 50にリセットするのではなく、前に設定された原点(つまりその時点でのmouseX, mouseY)を基準にした50, 50に原点を設定します。

行列のpushとpop

前のサンプルではうまく利用できませんでしたが、変換は累積するという事実は多くの場合、役に立つ振る舞いです(なぜそうなのか、translate()を呼び出すたびに原点自体がリセットされる世界で、少し離れた2つの顔がマウスに追従するスケッチを記述する場面を想像してみてください)。

とは言え、変換が分離できるようになると便利になります。分離によって、コードの一部は画面のある点を原点とし、コードの別の一部はまた別の点を原点にできるようになるからです。これを実行する最も簡単な方法はpush()とpop()の使用です。

push()関数は次のように言います。「ねえProcessing。ここから後のtranslate()への呼び出しを全部覚えておいて。後で元に戻したいから」。pop()関数は次のように言います。「ねえProcessing。translate()の追跡を思い出して。今元に戻して。ありがとう、さようなら」。

次のスケッチは、push()とpop()を使って前の例を書き直したものです。元の希望通りに、1つの顔はマウスに追従し、もう1つは常に画面左上隅にいつづけます。


関数の自作

観察眼があり怠け好きな人は、上記サンプルにはなはだしい繰り返しがあることに気づかれたことでしょう。draw()内に、顔を描画するまったく同じコードが2回出て来るのです。この繰り返しを何とかしてくくり出すことができたらいいのですが。つまり、ソースコードのどこかに1回だけコードを記述し、それに名前を与えて、その名前を入力したらいつでもそのコードを実行するようJavaScriptに伝えるということができたら、実行したい場所に毎回コードをコピー&ペーストする必要はなくなります。

JavaScriptにはこのための便利な方法があり、関数が自分で定義できるのです。関数は、名前を与えたステートメントの集まりで、関数によって、何度も何度も同じことを入力する必要がなくなります。

ここまで関数と呼ばれるものは多く扱ってきましたが、それらはどれもp5.jsとJavaScriptから与えられたものでした。しかし自分自身の関数の定義は簡単です。シンタックスは次の通りです。

function name_of_function() {
    statements
}

name_of_functionは、関数に与えたい名前と置き換えます。statementsは関数の中に置きたいコードと置き換えます。関数内では、関数呼び出しやforループ、ifステートメントなど、何でも記述できます。

ノート

関数名は変数名と同じルールにしたがって付ける必要があります(実を言うと関数は、舞台裏では、変数に代入できる、別の種類の値にすぎません)。

スケッチにこのコードを含めると、それは”関数定義”と呼ばれます。関数定義はソースコードのどこにでも置くことができますが、個人的な好みでは、setup()とdraw()の後の、ソースコードの下部が好きです。関数を”実行する”には(つまり、JavaScriptに対して、関数内のコードを実行するよう伝えるには)、関数名にかっこをつけて入力します。

次のスケッチは上記サンプルに関数を加えたものです。関数makeFace()を顔を描画するコードで定義し、draw()内でその関数への呼び出しを行っています。


しかしこれがすべてではありません。顔を描画するコードは関数なので、いつでも呼び出したいときに呼び出すことができます。以下は20個全部が重なり合う顔の描画です。


関数を使用すると、ソースコードでの繰り返しの量を減らすことができます。関数はまた、コードを論理的な単位に分割する機会を与えてくれるものでもあります。これは、上記サンプルで言うと、1回のmakeFace()への呼び出しならforループが読みやすくなる、という考えです。ループの実行内容がひと目で理解できます。また別のスケッチへの再利用も容易になります。ただ関数定義をコピー&ペーストすれば済みます。

スケール

描画命令に適用できる変換はtranslate()関数だけではありません。また別の変換に、描画するサイズを変更するscale()関数があります。translate()が座標システムの原点の位置を制御するのに対し、scale()は座標システムの各単位間の距離を制御します。

以下は前の例にもとづいた簡単なデモで、makeFace()関数を3回呼び出し、顔を半分のスケールと、通常のスケール、倍のスケールに設定しています。


以下はまた別のサンプルで、マウスに追従し、scale()変換を使ってサイズの単振動を行う2つの顔を描画します。


次もまた別のサンプルです。ランダムなサイズとストロークカラーで50個の顔を描画します。


scale()関数は、X方向とY方向別々にスケールすることができます。そのためには、関数呼び出しに2つめのパラメータを含めます。1つめのパラメータにはXスケールを、2つめのパラメータにはYスケールを指定します。これは、イメージを縦向きか横向きに”引き延ばす”効果があります。次もランダムな顔の例ですが、両方向にランダムにスケールさせています。


回転

最後に取り上げる変換は回転です。rotate()関数は座標システムの向きを変更します。おそらく、サンプルを見るのが最も早い理解の方法でしょう。


ご覧のように、forループの各繰り返しでは、line()への呼び出しはどれもまったく変わりません(同じline(0, 0, 350, 0)を実行している)。が回転が異なります(rotate((PI/2) * (0.1 * i))のiが変化する)。0の回転では、(0 , 0)からの線は原点から右に350ピクセル伸びて見えます。座標軸の回転によって、原点を固定させたまま、(350, 0)の位置が時計回りに回転することになります。

rotate()命令はパラメータをラジアン単位の値として解釈します。これは、piの値(約3.14)が円の半周に、piの2倍の値(約6.28)が円の一周に相当するということです(それ以上の値と回転は最初から再スタートするように見えます)。

次のスケッチは、makeFace()を呼び出し、マウス位置を使って顔を回転させます。


回転と移動

顔を画面に隅に描くのは拍子抜けというものです。では、画面中央にtranslate()を呼び出して描画してはどうでしょう? 以下はその試みです。


うまくいくだろうと思ったのですが、まったくだめでした。ここでは、rotate()を呼び出してその後translate()を呼び出していますが、これは、まず座標システムの向きを変えて、その後回転した座標で移動する、ということです。したがってtranslate(150, 150)を呼び出しても、右に150ピクセル、下に150ピクセル移動するのではなく、座標システムの向きに沿って150ピクセル移動し、そこから垂直に150ピクセル移動することになります。

最初に移動し(これはデフォルトの座標システムで行われます)し、次に回転すると、希望に近い結果になります。


この2つのサンプルは、変換は累積するので、変換の順番が重要になるという事実を示しています。変換はそれぞれ、特定の方法で座標システムに影響を与え、それ以降の変換は、それまでに変換された座標システムに適用されるのです。

こうした変化の視覚化や推測は難しいので、時間をとって変換関数とその適用順をいろいろ試し、働きの感触をつかむのが良いでしょう。経験則から言うと、単純な場合に当てはまる妥当な順番は、まず移動(translate()、希望する位置に原点を移す)、次に回転(rotate()、シェイプを描画する向きを変える)、最後にスケール(scale()、単位の大きさを変える)です。

原点を真ん中にする設計

初心者プログラマーが共通して直面するのは次の問題です。”彼らは、図形を描画する命令が(0, 0)を基準とするよう、そしてすべてのシェイプが左上隅にあるその座標で描画されるように、すべての描画コードを記述した。その後彼らは、上記サンプルのようなスケッチの振る舞いがおかしいことに驚く。” ”このシェイプを回転する”と言うとき、直感的に頭に描くのは、”このシェイプをそのセンターを中心に回転する”ということです。しかしコードでは、原点(0, 0)が左上隅にあると想定されているので、rotate()を呼び出すと、シェイプはその左上隅を中心に回転することになるのです。

上記スケッチでこの問題を修正する最も簡単な方法は、顔のセンターが(0, 0)に来るように関数を書き直すことです。そのためには、少し奇妙なことをして座標の一部に負の数を使う必要があります。次のスケッチは、この変更を実装した新しいバージョンです。


矩形のセンター

お気づきかもしれませんが、ビルトインのProcessing描画関数はシェイプの左上隅を中心とします。たとえばrect()関数はデフォルトで、初めの2つのパラメータを矩形の左上隅のXとY位置として使用するので、矩形の中心で回転させたいときには障害になります。しかしありがたいことに、Processingでは、その2つのパラメータを矩形の左上隅ではなく、矩形の中心として使用したいことが指定できるrectMode()関数が提供されています。以下はその違いを示すサンプルです。


ご覧のように、rectMode(CENTER)の呼び出しによって、Processingは与えられた座標をそのセンターに据える矩形を描画しますが、rectMode(CORNER)(デフォルトの振る舞い)は、与えられた座標をその左上隅にする矩形を描画します。

不気味な結末

次のサンプルは、ランダムな位置とサイズ、回転を使って、何十もの顔を描画します。


コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA