[Go to “teaching” in Morimoto Lab]
この講義では、関数と再帰を用いて「ルールから形を生成する方法」を学びます。
最終的には、自分でルールを設計し、オリジナルのパターンを作ることを目標とします。
また、再帰関数は少し特殊な関数の使い方をしますが、今後の講義やプログラミングでは、関数で実装した場合の効率性を考えて、積極的に関数で実装してみるようにしましょう!
オリジナルの関数を定義するときに、関数の頭についているvoidやintなどは関数の戻り値の型と言います。(Processingのsetup()にはvoidが付いていますね!それです)
この型は、関数が返す値によって決めます。
返す値のことを返り値や戻り値と言い、関数内でreturnを記述することで返します。
ちなみに、voidはよく関数の頭についていますが、変数の定義に使ったことはありませんでした。
voidは「この関数は何も返しませんよ!」という意味になります。
この場合、return文は省略可です。または、return ;などとして、何も返さない指定をします。
※return ; は関数内のその後のコードを無視してくれるのが結構便利です。
関数には値を入力することができます。これを引数と言います。つまり関数は何かの入力に対して値を返すもので、まさに数学の関数(f(x)=…)ですね。
キーワード(上記のまとめ)
戻り値の型:関数の定義のとき、頭についているvoid, int, float,…などのこと。
戻り値(返り値とも):関数の処理によって返す値のこと。
return文:戻り値を返すときにreturn a;などと、returnを使って値を返す。return文以降の関数の処理は行われない。
void:関数の型で使われた場合、「この関数は何も返しませんよ」という意味。この場合、関数にreturn文は不要。
引数:関数への入力として使われる値。
以下のプログラムを実行して、説明を読みながら、中身を少しアレンジして挙動を確認してみてください。
int a;
void setup()
{
size(300,300);
a=0;
//↓定義した関数を呼び出しています。setupから呼べばhelloは一回だけprintlnされます。
myFuncVoid();
}
void draw()
{
//↓もしここで呼ぶと怖いくらいたくさんhello!と言われます
//myFuncVoid();
//↓returnがある関数でも代入しなければその値は特に使われません
myFuncInt();
//↓returnのある関数は直接代入に使えます。boolなら直接if文に入れてもOKなど。
float aaa = myFuncFloat(5.0);
}
//↓voidがついた関数は何も返しません
void myFuncVoid()
{
println(“Hello!”);
}
//↓intなどのデータ型が付いた関数は、そのデータ型の変数を返します
int myFuncInt()
{
int b = 0;
return b;//関数の最後にreturnで返したい変数を返します。
}
//↓括弧の中に引数を定義できます。渡したい値があるときに使います。複数定義可能。
float myFuncFloat(float b)
{
return a + b + 1.5f;//このaはグローバル変数のaです
}
Note: 関数の 引数 は以下のように複数に増やすことができます.
float myFuncFloat(int a, float b, color c, PImage img)
Note: 関数を呼び出すときに与える引数は、定義されたデータ型と一致していればOKです。
呼び出し側は、数値でもいいですし、変数で指定する場合も同じ名前を使う必要などはありません。Note: メソッドというのも関数とかなり似た言葉で、関数の一種です。
クラスの中で定義したメンバ関数のことをメソッドと、厳密には言うみたいです。
メンバ関数というのはクラスに属する関数のこと、
クラスはまたのちの講義で説明します(あまり今は気にしなくてよいです)。
以下のヒントをよく読んで進めてください。
ヒント0:Processingに限らず、2次元のPC画面の原点は左上になり、y座標は上下逆転していることを思い出しておきます。
ヒント1:まず関数を使わずに極座標系で円の式で終点を計算し、始点を原点とした線を描画してみましょう。そのあと関数化するとよいです。角度を少しずつ増やしてアニメーションさせてもイメージしやすいかもしれません。
ヒント2:極座標系の式を使うとよいです。
ヒント3:sin, cosの使い方はリンク先を見てください
ヒント4:上記のsin, cosにはラジアンで入力しないといけません。processingに用意されているPIを使って計算してもいいですし、radians()という関数を使うと角度をラジアンに変換してくれます。
ヒント5:
描画したい始点の位置をx,yとした場合、原点中心の線が描画できたら両端にx,yを加算すると、始点を中心に指定した角度の線が描けます。
(課題ではx,y,angleの引数で線が描画できていれば可です。つらい人は次の再帰関数のサンプルを見てみてください。)
原点周りでできた線をそのままスライドして移動させるようなことを、「平行移動」と言います。
これはアフィン変換の一部です。
CG基本知識:アフィン変換とローカル&グローバル座標(上記ヒント5に関連)
上記のように、CGや画像処理では、平行移動、回転、拡大縮小などのアフィン変換をよく扱います。
Processingでは以下のような関数でそれらを簡単に扱うことができます。
https://processing.org/reference/pushMatrix_.html関連して、ローカル座標とグローバル座標という考え方を紹介します。
個々のモデルの作成(ローカル)と、全体のシーンでの配置(グローバル)は別にした方が扱いやすいという思想に基づいています。
原点周り(ローカル)で編集したものを、全体(グローバル)のどこかに配置する、という、ここでも部品と配置の考え方が出てきます。
これは配列を思い出したり、関数への引数が配列の場合の渡し方に決まりがある(下記)ので、少し難しいです。
Hint: 配列の要素数は.lengthで取得できます。関数内でここを定数などにしてはいけません。エラーが起こります。参照
Hint2: 配列の関数への渡し方は決まりがあるので、最低限の例を以下に書きます。
//定義の例:記述場所などは考えてみて下しさい
int[] xx = new int[3]; //xの位置
//値の代入はxx[0]=...などでもできますが、forを使うと便利です。
//自分で書いてみましょう。
//関数の定義は以下のようになります
void drawText(int xx[]){
//中身は書いて下さい
}
//関数の使用は以下のようになります
drawText(xx);
この段階では再帰は使わず、手動で作成してください。
この課題では、再帰を使わずに木を書く大変さがわかればいいので、時間がかかりすぎるようなら分岐一回くらいの木でも大丈夫です。
※Ex Aで作成した関数を「部品」として使います。
※Ex Bのような「同じ構造の繰り返し」を自動化するのが次の章で行う再帰関数です。
再帰関数で描画する対象として、最もポピュラーな「木」を描きましょう。
用意する関数は一本の枝と見立てた「一本の線」を描く関数です。
この関数は描画の「始点・角度・深さ」を引数とする関数であれば、最低限の木が描けます。
void setup(){
size(500,300); //window size
background(255); //background color
strokeWeight(5); //line weight(sen haba)
stroke(50,200,50); //line color RGB
branch(width/2, height, -90, 10);
}
void branch(float sx, float sy, float angle, int depth){
if(depth>0){
float rad = radians(angle);
float radius = depth * 5;
//end point
float ex =sx + cos(rad)* radius;
float ey =sy + sin(rad)* radius;
line(sx, sy, ex, ey);
//branch(ex,ey,angle+20, depth-1);
//branch(ex,ey,angle-20, depth-1);
}
}
上記のプログラムでは枝を描く関数の中で、枝を描く関数を呼び出しています。
これによって再帰的な処理になります。
depthあと「何回枝を分岐するか」を表すカウンタです。
depthの初期値が大きいほど、枝の分岐が多くなり、木は細かい構造になります。
depthはbranch()が読みだされるほど小さくなり、0になったらif文でもうbranch()を呼ばないようにすればプログラムを終了することができます。
!終了処理がうまく働かないコードを作ってしまった場合、branch()が無限に呼び出され、ソフトウェアを強制終了させる事態になりますので、気を付けてください!
💡一度、あえて終了条件を入れずに実行してみましょう💡更に枝の太さ、色、長さ、を考慮するために工夫すると、もっとリッチな木が描けます。
枝も線でなく、文字やお花などに変更したり、付け加えたりすれば表現力∞ですね!
以下の課題を通して、オリジナルの木を表現してみてください。
関数や再帰を用いた「パラメータを変えると見た目が変わる仕組み」を用いた上で、
以下のいずれか、または複数を満たすように工夫してください。
ヒント:
引数を増やすなどし、最大階層数を関数内で使えるようにすれば、depth/最大階層数、が0~1のグラデーションの値になり、色のパラメータなどとして使えるようになります。
HSB色空間は、H:色相、S:彩度、B:明度、で表され、グラデーションなどを設定する際にRGBより直感的です。公式ページの説明を見て、設定してみてください。
(HSB色空間についても調べるとよいです)オリジナルの木はランダムを使ったりしてもよいですし、枝以外のものを使っても面白いかもしれません。参考web
random()関数は手っ取り早く動かしたり、不揃い感を出すのに便利です。
HSB色空間
例えば?
シダ植物(Processingじゃないですけど参照)
きれいな模様(曼荼羅?魔法陣?)
プラレール!(参考URL)
再帰なのか微妙ですが、ついでにスピログラフ
その他:思いつけば研究になるかも!
再帰のルールをさらに一般化したものとして、L-Systemがあります。
再帰が「処理の書き方」なのに対し、L-Systemは「ルールの書き方」です。
ルールを文字列として定義し、それを展開することで複雑な形を生成できます。
L-systemについて以下のURLから、どのようなものがあるか見てみて、ルールや構造が見た目にどのように影響があるのか検討してみましょう。
可能なら実装にも挑戦してみてください(後日学ぶ「クラス」を知ってないと少し難しいですが…。)
自己相似図形やフラクタル図形のような形状を幅広く記述できます。
https://ja.wikipedia.org/wiki/L-system
英語ですが、こちらのテキストが分かりやすく、
Processingのコードもあります。
http://www.erase.net/projects/l-systems/
Processing official web site > Example > Fractals and L-Systems
の
Penrose Snowflake
Penrose Tile
Pentigree
にもサンプルがあります。
色々なパターンを作るためのL-Systemの構成?は以下のページが参考になります。
http://hiiragicompany.net/ls.html
一番見やすそうなのは、Coding ChallengeのL-systemでした!
上記の【コード】です。
原生生物もL-systemで描けるかもしれない