Post on 14-Feb-2021
数理生物学演習
コンピュータでアニメーションをつくる。動物行動を題材として。
1
目標 • Processingを知る • 簡単なアニメーションを作れるようになる
課題 • Processingを用いて、おもしろいアニメーションを作り、工夫した点について教えてください。プログラムを添付してください。
• 提出先は、yukikubo@kyudai.jp(久保) • 本文中に、所属・名前・学籍番号を書いてください。 • 締め切りは、7月22日(水)です。
2
Processingとはなに
• Processingは、可視化表現に使うために作られたプログラミング言語です。
• アニメーションを直感的に書き表していくことができます。
• Processingのウェブサイト https://processing.org
アプリケーションがない場合はダウンロードしてください。 (大学のMacにはあらかじめ導入されているはずです。) • Javaベースの言語なので、Cの文法と似てます。
3
• Finder → アプリケーション → Processing
Processingを起動しましょう
4
画面をつくる
void setup() {size(400, 400);
}
エディタに書きます。
ここを押します。 再生ボタンみたいなところ。
→
画面ができます。
Reference
size(x, y);画面の(x, y)座標の最大値を指定する。
5
画面には座標がある
void setup() {size(400, 400);ellipse(0, 0, 30, 30);
}
エディタに書き加えます。
Reference
ellipse(x, y, a, b);円を描かせるFunctionです。 (x, y)は円の中心の座標、a, bはそれぞれ、円の横幅(a), 円の縦幅(b)を表します。
a
b
画面左上が(x, y) = (0, 0)と初期設定されているので、左上の角を中心とした円が描かれます。
画面右下は(x, y) = (400, 400) 6
ランダム・ウォーク • ランダム・ウォークとはなに ランダム・ウォークは「気体分子の動き」から「ギャンブラーがカジノで一日に使うお金の動き」にいたるまで、現実で起こる現象をモデル化するときに使われています。 ・ランダム・ウォークの考え方 コイントスを二回して、表裏の組を考えます。4組の出方の確率は等しい。 対応した方向に動き、またコイントスを二回して、……を繰り返すとランダム・ウォークになります。
Flip 1 Flip 2 Result 表 表 前 表 裏 右 裏 表 左 裏 裏 後
7
ランダム・ウォークのプログラム • ランダム・ウォークのプログラムを作ります。 自身の座標を(x, y)とすると、ランダム・ウォークのプログラムは以下のように表されます。
int x;int y;void setup() {
size(400, 400);background(255);x = width/2;y = height/2;ellipse(x, y, 5, 5);
}void draw() {
int choice = int(random(4));if (choice == 0) {
x++;} else if (choice == 1) {
x--;} else if (choice == 2) {
y++;} else {
y--;}
ellipse(x, y, 5, 5);}
8
ランダム・ウォークの実行例 • ランダム・ウォークのプログラムを実行すると、下図のように、小さい円がウィンドウの真ん中(初期位置)から、ランダムに動き始めます。黒い部分は動いた軌跡です。
9
ランダム・ウォークのプログラムの解説1 int x; ← x座標を宣言int y; ← y座標を宣言void setup() {
size(400, 400);background(255);x = width/2;y = height/2;ellipse(x, y, 5, 5);
}void draw() {
int choice = int(random(4));if (choice == 0) {x++;
} else if (choice == 1) {x--;
} else if (choice == 2) {y++;
} else {y--;
}
ellipse(x, y, 5, 5);}
Reference
setup()
setup()はプログラムが始まったときに一度だけ実行されるものです。初期状態を決めるために使われます。スクリーンサイズ (size(x, y))や、背景色 (background())を指定します。
draw()
draw()は、その中に含まれるプログラムを繰り返し実行します。アニメーションは静止画の繰り返しで作られるので、ここが重要なポイントとなります。
setup()で初期設定し、draw()で繰り返して、アニメーションを表現します。
10
ランダム・ウォークのプログラムの解説2 int x; int y; void setup() {
size(400, 400);background(255);x = width/2;y = height/2;ellipse(x, y, 5, 5);
}void draw() {
int choice = int(random(4));if (choice == 0) {x++;
} else if (choice == 1) {x--;
} else if (choice == 2) {y++;
} else {y--;
}
ellipse(x, y, 5, 5);}
Reference
background()
background()はウィンドウの背景色を指定します。
setup()ではウィンドウサイズ、背景色、円の初期位置を決め、円を1回描いている。
background(51);
background(255, 204, 0);
widthsize()で決めた横幅の値を返す。
heightsize()で決めた縦幅の値を返す。
11
ランダム・ウォークのプログラムの解説3 int x; int y; void setup() {
size(400, 400);background(255);x = width/2;y = height/2;ellipse(x, y, 5, 5);
}void draw() {
int choice = int(random(4));if (choice == 0) {x++; ←右へ移動
} else if (choice == 1) {x--; ←左へ移動
} else if (choice == 2) {y++; ←上へ移動
} else {y--; ←下へ移動
}
ellipse(x, y, 5, 5);}
Reference
random()
random(a)は0以上a未満の値をfloatで返す。つまり、a=1だとすれば、random(1) = 0.31415などの値を取ります。
random(4)で0以上4未満の値を得るが、それをint(random(4))とすることで、小数点以下を切り捨てて、とり得る値を0,1,2,3のいずれかに限定する。 このとき、それぞれの値が出る確率は等しいので、それぞれの値に対して方向を指定すれば、ランダムウォークをさせることができる。
12
ランダム・ウォークのプログラムの解説4 int x; int y; void setup() {
size(400, 400);background(255);x = width/2;y = height/2;ellipse(x, y, 5, 5);
}void draw() {
int choice = int(random(4));if (choice == 0) {x++;
} else if (choice == 1) {x--;
} else if (choice == 2) {y++;
} else {y--;
}background(255);ellipse(x, y, 5, 5);
}
最後にellipse()で円を描く。 プログラムの流れとしては、 i. setup()で初期設定 ii. draw()内、random()を使って0,1,2,3
の値を同確率で出させる。 iii. 0,1,2,3のそれぞれの値に方向を指定す
る。(x, y)の座標を更新。 iv. 新しい(x, y)座標を使って円を描く。 v. ii.~iv.を繰り返す。Got it!!(^ω^)b
ここに、background(255)を加えると、軌跡が消えます。試してみてください。 background()は塗りつぶしのイメージ。挿入する位置に注意。
13
円の動き(座標の変化)は以下の式で表されます。 この4変数を繰り返し更新して移動を表現します。
ベクトルで動きを表す • ベクトルで個体の動きを表します。 • 個体に位置(x, y)と速さv、向きθの変数を持たせます。
(0, 0)
(x, y)
(xnext, ynext )
θv
xnext = x + vcosθynext = y+ vsinθ
14
周期境界で動かす • 端がつながっている空間で動かします。
double x;double y;double v;double d;void setup() {
size(400, 400);background(255);x = width/2;y = height/2;v = 4;d = HALF_PI;ellipse(x, y, 10, 10);
}void draw() {
x = x + v*cos(d);y = y + v*sin(d);
if (x < 0) { x = x + width;}if (y < 0) { y = y + height;}if (x > width) { x = x – width;}if (y > height) { y = y – height;}
background(255);ellipse(x, y, 10, 10);
}
15
非周期境界で動かす • 端がつながっていない空間で動かします。反射する。
double x;double y;double v;double d;void setup() {
size(400, 400);background(255);x = width/2;y = height/2;v = 4;d = PI/3;ellipse(x, y, 10, 10);
}void draw() {
x = x + v*cos(d);y = y + v*sin(d);
if (x < 0) { x = - x; d = PI - d; } if (y < 0) { y = - y; d = TWO_PI - d; } if (x > width) { x = 2*width - x; d = PI - d; } if (y > height) { y = 2*height - y; d = TWO_PI - d; }
background(255);ellipse(x, y, 10, 10);
}
※境界指定に抜けがあるかもしれません。四隅とか。お許し下さい。 16
Reference
cos(a), sin(a)
角度aのコサイン、サインの値を返します。角度aはラジアンで与えられます (From 0 to PI*2)。
PI
円周率πの値を返します。ほかにも、 TWO_PI = 2π HALF_PI = π/2 QUARTER_PI = π/4 が使えます。
練習のためのアイデア ・向きの変数dを少しずつ変化させるようにしたらどうなるでしょうか。 例えば、
d = d + random(1) – 0.5;を書き加えると。
17
追うもの、追われるもの • 捕食者(追うもの)と被食者(追われるもの)の関係を表してみましょう。• まずは、基本のルールを決めます。
捕食者P
被食者R
1. 捕食者Pは被食者Rをいつも追いかける。
2. 被食者Rは捕食者Pが一定距離まで近づいてきたら逃げる。
3. 捕食者Pは非周期境界、被食者Rは周期境界で移動できるように試しにしてみます。
18
追う、追われるのプログラム1
double px;double py;double pv;double pd;double rx;double ry;double rv;double rd;double rr;double rEscape;void setup() { size(600, 600); background(255);
px = random(width); py = random(height); pv = 5; pd = random(TWO_PI); fill(100); ellipse(px, py, 10, 10); rx = random(width); ry = random(height); rv = 4; rd = random(TWO_PI); rr = 100; noStroke(); fill(50,20); ellipse(rx, ry, rr*2, rr*2); stroke(0); fill(255); ellipse(rx, ry, 10, 10);} //setup()閉じる
• 解説は後にあります。
つづく 19
追う、追われるのプログラム2
void draw() { double distance = sqrt((rx-px)*(rx-px)+(ry-py)*(ry-py)); px = px + pv*(rx-px)/distance; py = py + pv*(ry-py)/distance; pd += random(1) - 0.5; if (px < 0) { px = -px; pd = PI - pd; } if (py < 0) { py = -py; pd = TWO_PI - pd; }
if (px > width) { px = 2*width - px; pd = PI - pd; } if (py > height) { py = 2*height - py; pd = TWO_PI - pd; } background(255); fill(100); ellipse(px, py, 10, 10);
• つづきです。
つづく 20
追う、追われるのプログラム3
if (distance 0 && (ry-py)>0) { rEscape = atan((ry-py)/(rx-px)); } if ((rx-px)0) { rEscape = atan((ry-py)/(rx-px)) + PI; } if ((rx-px)
追う、追われるのプログラム4
rx = rx + rv*cos(rd); ry = ry + rv*sin(rd); if (rx < 0) { rx = rx + width; } if (ry < 0) { ry = ry + height; } if (rx > width) { rx = rx - width; } if (ry > height) { ry = ry - height; }
noStroke(); fill(50,20); ellipse(rx, ry, rr*2, rr*2); stroke(0); fill(255); ellipse(rx, ry, 10, 10);} //draw()閉じる
• つづきです。
22
追い、追われプログラムの解説1 double px; ←捕食者Pのx座標double py; ←捕食者Pのy座標double pv; ←捕食者Pの速さdouble pd; ←捕食者Pの向きdouble rx; ←捕食者Rのx座標double ry; ←捕食者Rのy座標double rv; ←捕食者Rの速さdouble rd; ←捕食者Rの向きdouble rr; ←捕食者Pの逃走開始距離double rEscape;void setup() { size(600, 600); background(255); px = random(width); py = random(height); pv = 5; pd = random(TWO_PI); fill(100); ellipse(px, py, 10, 10);
Reference
fill(a)
fill(a)は図形の内側を塗りつぶす関数です。グレースケールの場合は、a = 0で黒に、a = 255で白になります。 また、fill(a, b, c)とすることで、他の色も指定できます。
捕食者Pと被食者Rの変数をそれぞれ用意します。 fill()を使って、PとRの色を区別しようとしています。
つづく 23
追い、追われプログラムの解説2 rx = random(width); ry = random(height); rv = 4; rd = random(TWO_PI); rr = 100; noStroke(); fill(50,20); ellipse(rx, ry, rr*2, rr*2); stroke(0); fill(255); ellipse(rx, ry, 10, 10);} //setup()閉じる
Reference
noStroke()
noStroke()は、図形の枠線を消す関数です。逆に、
二つの円を描いていますが、被食者R自身と被食者Rの逃走開始距離を可視化したものです。逃走開始距離は視界範囲と考えてもいいでしょう。noStroke(), stroke(), fill()を用いて見やすくしています。
つづく
stroke(a)
stroke(a)は、図形の枠線の色を指定する関数です。
24
追い、追われプログラムの解説3 void draw() { double distance = sqrt((rx-px)*(rx-px)+(ry-py)*(ry-py)); px = px + pv*(rx-px)/distance; py = py + pv*(ry-py)/distance;
Reference
sqrt(a)
sqrt(a)は、aの平方根を返す関数です。
ここで新たに宣言した変数distanceは、捕食者Pと被食者Rの距離を取らせるものです。 捕食者Pは捕食者Rの位置に向かって動くので、その方向ベクトルの単位ベクトルは、 x成分:(rx-px)/distancey成分:(ry-py)/distance となり、それぞれに速さpvをかけることで、Pの変位を得ることができます。左図。
つづく
P (px, py)
R (rx, ry)
方向ベクトル(rx-‐px, ry-‐py)
25
追い、追われプログラムの解説4 if (px < 0) { px = -px; pd = PI - pd; } if (py < 0) { py = -py; pd = TWO_PI - pd; } if (px > width) { px = 2*width - px; pd = PI - pd; } if (py > height) { py = 2*height - py; pd = TWO_PI - pd; } background(255); fill(100); ellipse(px, py, 10, 10);
捕食者Pは非周期境界(端で反射する)の空間で動くので、それを指定しています。 fill(100)で黒に近い灰色を指定し、捕食者Pを表す円を描きます。
つづく 26
追い、追われプログラムの解説5 if (distance 0 && (ry-py)>0) { rEscape = atan((ry-py)/(rx-px)); } if ((rx-px)0) { rEscape = atan((ry-py)/(rx-px)) + PI; } if ((rx-px)
追い、追われプログラムの解説6 if ((rx-px)==0 && (ry-py)==0) { rEscape = 0; } if ((rx-px)==0 && (ry-py)>0) { rEscape = HALF_PI; } if ((rx-px)0 && (ry-py)==0) { rEscape = 0; } if ((rx-px)==0 && (ry-py) 0
28
追い、追われプログラムの解説7 rd = rEscape; rd = rd % TWO_PI; } else { rd += random(1) - 0.5; } rx = rx + rv*cos(rd); ry = ry + rv*sin(rd); if (rx < 0) { rx = rx + width; } if (ry < 0) { ry = ry + height; } if (rx > width) { rx = rx - width; } if (ry > height) { ry = ry - height; }
場合分けで得られた、逃走に適した向きrEscapeを次のときの向き(rd)とします。ただ、rdの定義域は 0 to PI*2 としたいので、% (modulo)で剰余をいちおう取っておきます。 捕食者Pが近くにいないときは、動きに変化をつけたいので、random()項を加えておきます (else{}内)。
つづく 29
追い、追われプログラムの解説8 noStroke(); fill(50,20); ellipse(rx, ry, rr*2, rr*2); stroke(0); fill(255); ellipse(rx, ry, 10, 10);} //draw()閉じる
最後に描画します。setup()内と同じ。
練習のためのアイデア ・行動の規則を増やしてみましょう。 たとえば、捕食者が加速できるとか。 被食者が瞬間移動できるとか。 なんでもいいです。 ・見た目を工夫してみましょう。 すごく近づいたときに色が変わるようにしてみる。 ・捕食者も周期境界で移動できるとしたらどうなるでしょうか。 ちょっと面倒ですが、興味のあるひとは考えてみてください。
30
課題 • Processingを用いて、おもしろいアニメーションを作り、工夫した点について教えてください。プログラムを添付してください。
• 質問や感想があれば書いてください。 • 提出先は、yukikubo@kyudai.jp(久保) • 本文中に、所属・名前・学籍番号を書いてください。 • 締め切りは、7月22日(水)です。
• 関数のより詳しい説明は、Processingのウェブサイトに。 https://processing.org/reference/
31
配列を用いた群れの行動 • 配列を用いて群れの行動を表してみましょう。• 基本のルールは1つ:群れの中の個体が向きを合わせようとすること。
32