目次
こんにちは。NUMです。
最近、Processingをやっていて幾何学模様でありつつ、生物らしさを作品に取り入れてみたいという想いが強くなっています。
僕が作品作りにおいて重視しているのが、デジタルながらもアナログの良さを表現することです。
手書き感のある質感を取り入れているのも、アナログならではの良さがあると考えているからです。
生物の動きにおいても予測不可能さがありながらも、全体として見ると群れの形に美しさがあり、アナログの良さを体現していると思います。
今回は生物の群れの美しさを取り入れるためにBoidsについて勉強していこうと思います。
この記事で使用するのはProcessingの公式サイトのBoidsプログラムです。
Boidsとは
Boidsとはエージェントに3つの規則を与えて、動物が群れで動く振る舞いをモデル化したアルゴリズムです。3つの規則とは「分離」「整列」「結合」です。
分離:個体は近づきすぎないように距離を取って移動する
整列:個体は同じ方向に向かって移動する
結合:個体は群れの中心に向かって移動する
ルールはたったこれだけです。僕はかなり理解に苦しみました(笑)
この3つのルールに従って動きの制御していきます。
動きの制御には操舵力の計算をするので、その概念について説明します。
操舵力
操舵力とはBoid(処理個体)が現在の速度から目標方向へ進むための「軌道修正量」になります。
目標方向から現在の速度を引くことで軌道の修正量を求められます。
steer(操舵力) = desire(目標方向) - currentVel(現在の速度)
目標方向は現在地の位置ベクトルから目標の位置ベクトルを引くことで求められます。
desire(目標方向) = currentLocation(現在地) - targetLocation(目標の位置)
操舵力を計算するためには、個体の位置、速度、加速度の情報が必要です。
そのため、参考サイトのプログラムではBoidクラスにメンバとして持たせています。
PVector position; // 位置
PVector velocity; // 速度
PVector acceleration; // 加速度
ここで加速度の情報がある理由は「分離」「整列」「結合」のそれぞれの力を加速度に足し合わせ、個体の位置更新で使用するためです。
(加速度の更新は最後に説明します。)
次は操舵力の計算を用いてBoidsの主要な概念である「分離」「整列」「結合」のアルゴリズムについて説明します。
分離
分離は個体が近づきすぎないように距離を取る役割です。
つまりBoid(処理個体)がBoid_Bに近づいた時、Boid_Bがいる方向とは
逆の方向(逃避ベクトル)に操舵するということです。
しかし、Boidの付近には複数の個体が存在している可能性があるので
特定の範囲内のBoids(その他個体)を探索、それぞれの逃避ベクトルを足し合わせて、逃避ベクトルの合計値をBoidsの数で割ります。
これでBoidsを加味した逃避ベクトルを得られます。
得られた逃避ベクトルは先ほどの操舵力の式に当てはめることで操舵力を計算できます。
PVector separate (ArrayList<Boid> boids) {
float desiredseparation = 25.0f; // 特定の範囲
PVector steer = new PVector(0, 0, 0);
int count = 0;
for (Boid other : boids) {
float d = PVector.dist(position, other.position);
// (d > 0)の条件は自身を計算対象に含めないため
if ((d > 0) && (d < desiredseparation)) {
// 逃避ベクトルの取得
PVector diff = PVector.sub(position, other.position);
diff.normalize();
// その他Boidとの距離が近い場合、逃避ベクトルを大きくさせたいため、重みをつける
diff.div(d);
steer.add(diff);
count++;
}
}
if (count > 0) {
steer.div((float)count); // 逃避ベクトルの平均
}
// 操舵力の調整
if (steer.mag() > 0) {
steer.normalize();
steer.mult(maxspeed);
// steer(操舵力) = desire(目標方向) - currentVel(現在の速度)
steer.sub(velocity);
// 加速度に制限をかけて滑らかな動きで目標へ向かう操舵力にする
steer.limit(maxforce);
}
return steer;
}
整列
整列は個体間が同じ方向に移動する役割です。
これはイメージしやすいですね。Boids全体の方向を把握するために平均速度を計算し、Boidの速度と平均速度の差分から整列の操舵力を求められます。
※プログラムは分離と処理内容がほぼ同じなので細かい内容は省略します。
PVector align (ArrayList<Boid> boids) {
float neighbordist = 50;
PVector sum = new PVector(0, 0);
int count = 0;
//Boidsの速度を合計
for (Boid other : boids) {
float d = PVector.dist(position, other.position);
if ((d > 0) && (d < neighbordist)) {
sum.add(other.velocity);
count++;
}
}
if (count > 0) {
sum.div((float)count); // Boids全体の方向
sum.normalize();
sum.mult(maxspeed);
PVector steer = PVector.sub(sum, velocity); // 操舵力
steer.limit(maxforce);
return steer;
} else {
return new PVector(0, 0);
}
}
結合
結合は個体間の中心位置に向かう役割です。
整列ではBoidsの平均速度でしたが、結合では平均位置を計算します。
平均位置を計算したら今までと同じように、現在位置と平均位置から目標方向を求めて、
目標方向と速度の差分から操舵力を求めます。
※プログラムは分離と処理内容がほぼ同じなので細かい内容は省略します。
PVector cohesion (ArrayList<Boid> boids) {
float neighbordist = 20;
PVector sum = new PVector(0, 0);
int count = 0;
//Boidsの速度を合計
for (Boid other : boids) {
float d = PVector.dist(position, other.position);
if ((d > 0) && (d < neighbordist)) {
sum.add(other.position);
count++;
}
}
if (count > 0) {
sum.div(count); // 中心位置
return seek(sum); // 中心位置への操舵力を求める
} else {
return new PVector(0, 0);
}
}
// 現在位置から対象位置へ向かう操舵力を求める
PVector seek(PVector target) {
PVector desired = PVector.sub(target, position); // 目標方向
desired.normalize();
desired.mult(maxspeed);
PVector steer = PVector.sub(desired, velocity); // 操舵力
steer.limit(maxforce);
return steer;
}
力の適用
今までは「分離」「整列」「結合」の操舵力を計算しました。
この力をBoidに適用させる必要があります。
そのため、applyForceメソッドを使用して加速度のメンバに操舵力を適用させます。
// 分離、整列、結合をそれぞれ計算し加速度に加える
void flock(ArrayList<Boid> boids) {
PVector sep = separate(boids); // 分離
PVector ali = align(boids); // 整列
PVector coh = cohesion(boids); // 結合
// 各力の重みづけ
sep.mult(1.5);
ali.mult(1.0);
coh.mult(1.0);
// 各力を加速度に加える
applyForce(sep);
applyForce(ali);
applyForce(coh);
}
// 加速度に引数の力を適用させる
void applyForce(PVector force) {
acceleration.add(force);
}
まとめ
Boidの中心的な概念の説明は以上となります。
プログラム全体は参考サイトに載っているのでぜひ遊んでみてください。
各力の重みの値を少し変えてみるだけでも、動きが大きく変わるのでめちゃめちゃ面白いです。
sep.mult(1.5);
ali.mult(1.0);
coh.mult(1.5);
以下は、Boidの軌跡を作品に取り入れてみたものです。
この作品のテーマは「生命と機械の対立」です。
Boidsの軌跡が映画『Matrix』に出てくるセンチネルのように見えます。
中心の生物たちの生存空間に、機械の大群が押し寄せてくるようなイメージです。
最近のAIの進歩と人々の反応を僕の視点で表現してみました。
Boidsの解説は以上となります。最後まで読んでいただきありがとうございました。
参考文献
Comments