JavaScript
processing.js

インタラクティブプログラミング - 群れ(boid)を作る

More than 3 years have passed since last update.

最近勉強しているもののメモです。

間違っている、こうしたほうが良いなどあれば是非教えていただきたいです。

グレイグ・レイノルズというひとが作った、コンピュータ空間上を自由に飛び回る鳥、をbirdoid(鳥もどき)という言葉が短くなったもの。

グレイグ・レイノルズはこのboidが本当に生き物が群れをなして飛んでいるように見せるために3つのルールを作った。


  • 多くの個体がいる方向に動く

  • 近くの個体と近づきすぎたらぶつからないように離れること

  • 近くのもの同士で動くスピードや方向を合わせる

この3つのルールを守って動くものがboidといえる。

ワリと実装が簡単そうで、かつ動きが面白かったので、入門したかった僕にピッタリではないかと思いやってみた。


実装

今回はprocessing.jsを使って実装してみた。

まず、動く個体、今回はこれをBirdとする、を実装することにする。

var Bird = function(x, y, vx, vy, id, others) {

this.x = x;
this.y = y; // x,y: Birdインスタンスの現在の位置
this.vx = vx;
this.vy = vy; // vx, vy: 移動量
this.id = id; // 個体を識別するid
this.others = others; // 他の個体たち
}

このようなBirdクラスを実装する。このクラスにメソッドを追加していく感じにする。

まず、上の3つの条件を満たすためにの関数を実装する必要がある。

今回は.check()という関数がその役割を担うことにして、その.check()関数の中で3つの条件をそれぞれ満たすメソッドを実行する。

3つの役割はそれぞれ、


  • 多くの個体がいる方向に動く

  • 近くの個体と近づきすぎたらぶつからないように離れること

  • 近くのもの同士で動くスピードや方向を合わせる

なので、それらをそれぞれ別の関数として実装する。これらをそれぞれ, center(), .avoid(),.average()というメソッドにする。

3つのメソッドでそれぞれだされる移動量をそれぞれベクトルにして、コンストラクタで宣言しておく。

こんな感じ。

var Bird = function(x, y, vx, vy, id, others) {

this.x = x;
this.y = y; // x,y: Birdインスタンスの現在の位置
this.vx = vx;
this.vy = vy; // vx, vy: 移動量
this.id = id; // 個体を識別するid
this.others = others; // 他の個体たち
// 追加
this.v1 = new PVector();
this.v2 = new PVector();
this.v3 = new PVector();
}

そして、最終的なBirdの移動量は、これらの移動量を足しあわせたものになる。

この時に、そのまま足し合わせると上手くいかないので、それぞれに値をかけて上手く行くようにする。(このへんは手で調節するしかないんだろうか...)

// こう定義しておいて

var r1 = 1; // パラメータ:群れの中心に向かう度合
var r2 = 0.8; // パラメータ:仲間を避ける度合
var r3 = 0.1; // パラメータ:群れの平均速度に合わせる度合

// こうなる
this.vx += r1*this.v1.x + r2*this.v2.x + r3*this.v3.x;
this.vy += r1*this.v1.y + r2*this.v2.y + r3*this.v3.y;

次に3つの条件を実装する。

まず


  • 多くの個体がいる方向に動く

という条件から。

// 予め定義しておく

var NUMBER = 10; // 全個体数
var CENTER_PULL_FACTOR = 300; // 群れの中心に引き寄せられる強さ。小さいほど引き寄せられる

// 群れの中心に向かう移動量
Bird.prototype.center = function() {
for(var i = 0, other; other = this.others[i]; i++){
// idが一緒 = 同じ個体なのでスキップ
if(this.id == other.id) continue;
// 全ての個体の値を足し合わせる
this.v1.x += other.x;
this.v1.y += other.y;
}

// 平均をとって、中心を求める
this.v1.x /= (NUMBER - 1);
this.v1.y /= (NUMBER - 1);

// 中心に近づく距離を計算
this.v1.x = (this.v1.x - this.x) / CENTER_PULL_FACTOR
this.v1.y = (this.v1.y - this.y) / CENTER_PULL_FACTOR
};

つぎに


  • 近くの個体と近づきすぎたらぶつからないように離れること

という条件

// 予め定義しておく

var DIST_THRESHOLD = 30; // どれくらいの距離までちかづいたら離れるか

Bird.prototype.avoid = function() {
for(var i = 0, other; other = this.others[i]; i++){
if(this.id == other.id) continue;
// 一定の距離以下になったら
if(dist(this.x, this.y, other.x, other.y) < DIST_THRESHOLD) {
// 離れるために、逆方向に動く
this.v2.x -= (other.x - this.x);
this.v2.y -= (other.y - this.y);
}
}
};

最後に


  • 近くのもの同士で動くスピードや方向を合わせる

Bird.prototype.average = function() {

for(var i = 0, other; other = this.others[i]; i++){
if(this.id == other.id) continue;
this.v3.x += other.vx;
this.v3.y += other.vy;
}

// 平均ををとって、中心を求める
this.v3.x /= (NUMBER - 1);
this.v3.y /= (NUMBER - 1);

// 全体のベクトルの半分の移動速度にする
this.v3.x = (this.v3.x - this.vx)/2;
this.v3.y = (this.v3.y - this.vy)/2;
};

これで全ての条件が実装できたので、これを.check()関数で実行する。

Bird.prototype.check = function(){

this.center();
this.avoid();
this.average();
}

次に、先ほど書いた、全てのベクトルを足し合わせる式を書いて、フレームごとに座標をアップデートするメソッド、.update()を実装する。

// 予め定義しておく

var SPEED = 4; // 最大の移動速度

Bird.prototype.update = function() {
this.vx += r1*this.v1.x + r2*this.v2.x + r3*this.v3.x;
this.vy += r1*this.v1.y + r2*this.v2.y + r3*this.v3.y;

// 距離を求める
var movement = Math.sqrt(this.vx * this.vx + this.vy * this.vy);
// 移動距離が一定以上だったら
if(movement > SPEED) {
// 減速させる
this.vx = (this.vx / movement) * SPEED;
this.vy = (this.vy / movement) * SPEED;
}

this.x += this.vx;
this.y += this.vy;
}

移動量のベクトルは1フレームごとにリセットしないといけないので

.clearVector()メソッドを追加する

Bird.prototype.clearVector = function() {

this.v1.set(0,0);
this.v2.set(0,0);
this.v3.set(0,0);
}

ここまで来たら描画するための条件は全てそろったので、.draw()メソッドを追加する

Bird.prototype.draw = function() {

this.clearVector();
this.check();
this.update();
// 円を描画する
ellipse(this.x, this.y, R * 2, R * 2);
// 移動する方向をしめす線を描画する
line(this.x, this.y, this.x + this.vx * 3 , this.y + this.vy * 3);
}

これでBirdクラスの実装はすべてそろったので、最後にprocessing.jsでこれを描画するメソッドを書く。

processing.jsはsetup()draw() という2つの関数内に初期化処理を描画処理を書くことで描画を実行できる。

var birds = [];

function getRandom(min, max) {
return Math.random() * (max - min) + min;
}

void setup(){
// canvasのサイズを定義
size(document.body.offsetWidth, document.body.offsetHeight);
// フレームレート
frameRate(20);
// 塗りつぶしをしない
noFill();
// 白色で書く
stroke(255, 255, 255);

// 円を全個体数で割る
var angle = TWO_PI / NUMBER;
// 円形に配置
for(var i = 1; i <= NUMBER; i++) {
var addx = Math.cos(angle*i);
var addy = Math.sin(angle*i);
birds.push(new Bird(
width / 2 + addx*50, height / 2 + addy*50,
getRandom(-SPEED,SPEED)*addx, getRandom(-SPEED,SPEED)*addy,
i-1,birds
));
}
}

void draw() {
background(0);
for(var i = 0; i < NUMBER; i++){
birds[i].draw();
}
}

これでboidが実装できた。

e6b081712ee245cb038be25f302afba5.gif

ソースコードはこちら


参考

ここのコードをとても参考にさせて頂きました。

http://www.catch.jp/wiki/?Processing.js%B4%F0%C1%C3%BA%C7%C2%AE%C6%FE%CC%E7

分かりやすかった。

http://www.1101.com/morikawa/2001-06-25.html