概要
Boids 2.1では、いよいよBoidsのルールの一つ「分離(Separation)」を導入する。分離行動の考え方は、ここでは以下の通りとした。
- 視野内に個体が存在するとき、最も距離が近い個体を選択する
- その直近個体と反対方向に、自身の進行方向を変化させる
分離のアルゴリズム
視野
個体は自身の進行方向の左右90度以内、180度の視野角を持ち、その視野角内で一定距離以内の個体に反応するものとする。
視野内外の判定の概念は下図の通りで、個体Pは視野内と判定され、個体Qは視野外として無視される。

視野内外判定の手順は以下の通り。
- 前後判定の方法により、視野角内の個体があればそれを選定
- その個体について ならば次へ進む ならば次へ進む
- その個体が最も近いなら入れ替え
回避動作
回避対象が自身の進路の右にいるなら左に、進路の左にいるなら右に、方向ベクトルの角度を変更する。進路変更の角度は予め設定しておく。

回避動作の手順は以下の通り。
- 左右判定の方法により、回避対象が進行方向の左右どちらにあるかを判定
- 回避対象が進行方向の左にあるなら右へ、右にあるなら左へ方向を少し変化させる。
ここで方向ベクトルの変化量θは予め設定しておき、これに対応するcos、sinによる回転行列を適用して方向を変える。cos、sinの値は、できるだけ計算量が少なくなるようにする。
コード内容
初期パラメータ定義
HTMLで定義する初期パラメータに、分離に関する以下の変数を追加する。
- 分離に関する視野の深さ(BD21_VIEW_FIELD_LENGTH)
- 回避行動の進行方向の変化(BD21_SEPARATION_ANGLE)
| 1 2 3 4 5 6 7 8 9 10 11 12 13 |   <script>   //<![CDATA[   BD21_V_MAX = 100;   BD21_CREATURE_SIZE = 10;   BD21_WALL_DETECTION_LENGTH = 100;   BD21_WALL_REPULSION_PARAM = 16;   // Boids 2.1で追加:視野の深さ   BD21_VIEW_FIELD_LENGTH = 100;   // Boids 2.1で追加:分離行動の速度の向きを変更する角   BD21_SEPARATION_ANGLE = 10 * Math.PI / 180;   BD21_INTERVAL_SEC = 0.05;   //]]>   </script> | 
クラス変数
初期パラメータの保存のため、Creatureクラスのクラス変数を追加する。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |   class Creature extends taustation_geom2d.MovingAgent     # private staticなクラス・プロパティ     # 最大速度     @_vMax: 100     # 描画の基本となるサイズ     @_drawSize: 8     # 壁の衝突回避を認識する距離     @_wallDetectionLength:  100     # 反発力パラメータ     @_wallRepulsionParam: 4     # 視野の長さ(奥行)     @_viewFieldLength: 100     # 分離行動の回避計算用     @_separationAngle: 0 | 
パラメータのセッター
Controlerクラスに、Creatureクラス変数へのセッターを定義する。
| 1 2 3 4 5 6 7 8 9 10 11 12 |   class Controler     .....     # 定数として扱うクラス変数を設定するメソッド群     setVMax: (vMax) -> Creature._vMax = vMax     setCreatureSize: (size) -> Creature._drawSize = size     setWallDetectionLength: (pix) -> Creature._wallDetectionLength =  pix     setWallRepulsionParam: (param) -> Creature._wallRepulsionParam = param     setViewFieldLength: (length) -> Creature._viewFieldLength = length     setSeparationAngle: (angle) -> Creature._separationAngle = angle     setIntervalSec: (@interval_sec) -> | 
パラメータのセット
HTMLで定義されたパラメータをControlerオブジェクトのセッターによってセットする。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | jQuery ($) ->   # Controlerを作成し、canvasオブジェクトを渡す   canvas = $("#Boids21_canvas")[0]   controler = new Boids21.Controler(canvas)   # 固定パラメータのセット   # パラメータの値は、Javascript形式でHTMLに埋め込まれている想定   controler.setVMax(BD21_V_MAX)   controler.setCreatureSize(BD21_CREATURE_SIZE)   controler.setWallDetectionLength(BD21_WALL_DETECTION_LENGTH)   controler.setWallRepulsionParam(BD21_WALL_REPULSION_PARAM)   controler.setViewFieldLength(BD21_VIEW_FIELD_LENGTH)   controler.setSeparationAngle(BD21_SEPARATION_ANGLE)   controler.setIntervalSec(BD21_INTERVAL_SEC)   # Generateボタンが押されたとき、指定された個体数で群をつくる   $("#boids21_generate").click ->     pop = $("#boids21_population").val()     if pop < 1       pop = 1       $("#boids21_population").val(pop)     controler.generate(pop)   # Start/Stopボタンが押されたとき、アニメーションの動作を切り替える   $("#boids21_start_and_stop").click ->     controler.startAndStop() | 
Ceatureクラスの変更 – separation()メソッドの追加
Creatureクラスのmove()メソッドがseparation()メソッドを呼び出すようにする。
| 1 2 3 4 5 6 7 8 9 10 11 12 |   class Creature extends MovingAgent     .....     move: (interval_sec) ->       # 壁を避ける       @wallAvoidance()       # 分離行動(separation)       @separation()       ..... | 
separation()メソッドを新たに定義する。
- 速度の方向を変化させる回転行列のcos、sinに対応したローカル変数cs、snを定義している
- 最も近い個体への参照がnearestCreatureプロパティに保存されるが、視野内に個体が存在しないときはnullとなる
- 速度の回転方向は、右がマイナス方向、左がプラス方向となることに注意
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |     # Boids2.1     # 分離行動(separation)     # 前方視角180度内で一定距離内の視野の中で、最も近い個体を避ける     # 相手方の速度の向きには関係なく、相手側と反対方向に速度の向きをを少し変更する。     separation: ->       # 回避行動の方向転換計算用cos/sin       cs = Math.cos(Creature._separationAngle)       sn = Math.sin(Creature._separationAngle)       # 自身に最も近い個体       @nearestCreature = null       # 自分以外の群のすべての個体についてサーチ       for other in @cluster.creatures         if other != this           # その個体が自分の前方にいるなら           if other.isInFrontOf(this)             d = Vector.distance(this.pos, other.pos)             # その個体が視野の中にいるなら             if d <= Creature._viewFieldLength               # 既に個体がセットされていて、今回の個体の方が近ければ入れ替え               # 最初に認識した個体ならそのままセット               if @nearestCreature?                 if d <= Vector.distance(@nearestCreature.pos, this.pos)                   @nearestCreature = other               else                 @nearestCreature = other       # 近くの個体が視野内に存在するなら回避計算       if @nearestCreature?         # 相手が自分の右にいるなら左へ、自分の左にいるなら右へ向きを変更         if @nearestCreature.isOnTheRightOf(this)           vx = @v.x * cs - @v.y * sn           vy = @v.x * sn + @v.y * cs         else           vx =  @v.x * cs + @v.y * sn           vy = -@v.x * sn + @v.y * cs         @v.x = vx         @v.y = vy |