概要
Boids 2.0は以後のBoids 2.xシリーズの土台となるもので、Boids 1.2に対して以下を変更している。
- 位置と速度を持った
Creature
クラスのうち、位置と速度の保持、互いの位置関係の判定に関する性質を汎用のMovingAgent
クラスとしてまとめ、taustation-geo2d
パッケージに含めた - 初期パラメータをHTMLのスクリプトに置き、再コンパイルしなくてもパラメータを変更しやすくなるようにした。
MovingAgentクラスの導入
Creature
クラスはMovingAgent
クラスを継承し、それらのメソッドを利用する。
1 |
class Creature extends MovingAgent |
MovingAgent
のプロパティを継承するため、位置と速度の指定が変更。たとえばCreature
クラスのインスタンスがcreatureだとすると、以下のようになる。
- 位置のx座標は、
creature:pos.x
- 位置のy座標は、
creature:pos.y
- 速度のx成分は、
creature:v.x
- 速度のy成分は、
creature:v.y
HTMLでの初期パラメータ設定
概要
各種パラメータの設定を、CoffeeScriptの再コンパイルやアップロードをしなくても、HTMLで設定・変更できるようにした。
- 必要なパラメータをHTMLのスクリプトで定義
- 以下のパラメータをセットするセッターを、全て
Controler
クラスのメソッドとして集約- 個体に関するパラメータは
Creature
のクラス変数 - アニメーションのインターバルは
Controler
クラスのプロパティ
- 個体に関するパラメータは
- スクリプト読み込み時に、
Controler
オブジェクトのセッターにグローバル・パラメータ変数を渡してパラメータ設定
初期パラメータ定義
HTMLにパラメータをグローバル変数としてスクリプト記述。
注意点は、スクリプトはJavascriptにコンパイルされているので、直接読み込まれるグローバル変数もJavascriptの変数として書くこと。特に各定義の文末にセミコロン(;)を付けること。
1 2 3 4 5 6 7 8 9 |
<script> //<![CDATA[ BD20_V_MAX = 100; BD20_CREATURE_SIZE = 10; BD20_WALL_DETECTION_LENGTH = 100; BD20_WALL_REPULSION_PARAM = 16; BD20_INTERVAL_SEC = 0.05; //]]> </script> |
クラス変数
上記のパラメータのうち、個体の運動に関わるものを、Creature
クラスのクラス変数として定義。
なお、アニメーションのフレーム・インターバル(interval_sec
)は、Controler
クラスのクラス変数として定義している。
1 2 3 4 5 6 7 8 9 10 |
class Creature extends taustation_geom2d.MovingAgent # private staticなクラス・プロパティ # 最大速度 @_vMax: 100 # 描画の基本となるサイズ @_drawSize: 8 # 壁の衝突回避を認識する距離 @_wallDetectionLength: 100 # 反発力パラメータ @_wallRepulsionParam: 4 |
パラメータのセッター
上記のクラス変数にパラメータをセットするセッターを、Controler
クラスのメソッドとして集約。
1 2 3 4 5 6 7 8 9 10 |
class Controler ..... # 定数として扱うクラス変数を設定するメソッド群 setVMax: (vMax) -> Creature._vMax = vMax setCreatureSize: (size) -> Creature._drawSize = size setWallDetectionLength: (pix) -> Creature._wallDetectionLength = pix setWallRepulsionParam: (param) -> Creature._wallRepulsionParam = param setIntervalSec: (@interval_sec) -> |
パラメーターのセット
スクリプト実行時に、上記セッターを通してグローバル変数のパラメータを設定する。
1 2 3 4 5 6 7 8 9 10 11 |
jQuery ($) -> ..... # 固定パラメータのセット # パラメータの値は、Javascript形式でHTMLに埋め込まれている想定 controler.setVMax(BD20_V_MAX) controler.setCreatureSize(BD20_CREATURE_SIZE) controler.setWallDetectionLength(BD20_WALL_DETECTION_LENGTH) controler.setWallRepulsionParam(BD20_WALL_REPULSION_PARAM) controler.setIntervalSec(BD20_INTERVAL_SEC) |
Boids 2.0の全コード
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
# Boids 2.1 # Boids 2.0 = 1.2に基づいて、分離(separation)ルールを導入する。 Boids20 = {} # パッケージBoids20の本体 do -> # 独自クラスのエイリアス # パッケージtaustation_geom2d.jsが必要。 Vector = taustation_geom2d.Vector MovingAgent = taustation_geom2d.MovingAgent # Boidsが活動する空間のクラス # このバージョンでは、canvasオブジェクトの上下左右の座標を持つ長方形 class Field constructor: (canvas) -> @xMin = 0 @yMin = 0 @xMax = canvas.width @yMax = canvas.height # Boidsの個体を実現するクラス # 位置座標と速度ベクトルの成分を保持する # 自らが活動するFieldクラスのオブジェクトへの参照も保持する class Creature extends taustation_geom2d.MovingAgent # private staticなクラス・プロパティ # 最大速度 @_vMax: 100 # 描画の基本となるサイズ @_drawSize: 8 # 壁の衝突回避を認識する距離 @_wallDetectionLength: 100 # 反発力パラメータ @_wallRepulsionParam: 4 # コンストラクタの引数にはFieldオブジェクトを渡す # 位置、速度、加速度、自分が属する群、Fieldをプロパティとして持つ constructor: (cluster, field) -> super() @a = new Vector(0, 0) @cluster = cluster @field = field @nearestCreature = null # 個体を描画するメソッド # canvasのコンテキストを引数で受け取る draw: (ctx) -> v = @v.abs() sx = Creature._drawSize * @v.x / v sy = Creature._drawSize * @v.y / v p = 0.25 ctx.fillStyle = "black" ctx.beginPath() ctx.moveTo(@pos.x + (1 - p) * sx, @pos.y + (1 - p) * sy) ctx.lineTo(@pos.x + (-sx - sy) * p, @pos.y + (sx - sy) * p) ctx.lineTo(@pos.x + (-sx + sy) * p, @pos.y + (-sx - sy) * p) ctx.closePath() ctx.fill() return # 個体を動かすメソッド # 「群れ」を表すClusterクラスのmove()メソッドから呼ばれる # interval_secは秒単位のアニメーション・フレームのインターバル move: (interval_sec) -> # 壁を避ける @wallAvoidance() # 加速度を考慮した速度の変化を計算 # 速度の絶対値が増え続けないようにキャップで抑える # 速度成分の符号を得るのに、Math.signがサポートされていないブラウザが多いため、 # 値/絶対値で符号を得ている @v.plusEq(@a.times interval_sec) @v.x = @v.x / Math.abs(@v.x) * Math.min(Math.abs(@v.x), Creature._vMax) @v.y = @v.y / Math.abs(@v.y) * Math.min(Math.abs(@v.y), Creature._vMax) # インターバルの間の移動量を計算 @pos.plusEq(@v.times interval_sec) # 壁にぶつかった場合は全反射 if @pos.x <= @field.xMin or @pos.x >= @field.xMax then @v.x = -@v.x if @pos.y <= @field.yMin or @pos.y >= @field.yMax then @v.y = -@v.y return # 壁を避ける行動 # 壁からの距離に応じた斥力を(repulsion)想定し、x/y方向の加速度を計算 wallAvoidance: -> if @pos.x <= Creature._wallDetectionLength @a.x = Creature._wallRepulsionParam * (Creature._wallDetectionLength / @pos.x) ** 2 else if @pos.x >= @field.xMax - Creature._wallDetectionLength @a.x = -Creature._wallRepulsionParam * (Creature._wallDetectionLength / (@field.xMax - @pos.x)) ** 2 else @a.x = 0 if @pos.y <= Creature._wallDetectionLength @a.y = Creature._wallRepulsionParam * (Creature._wallDetectionLength / @pos.y) ** 2 else if @pos.y >= @field.yMax - Creature._wallDetectionLength @a.y = -Creature._wallRepulsionParam * (Creature._wallDetectionLength / (@field.yMax - @pos.y)) ** 2 else @a.y = 0 # Boidsの群を管理するクラスで、以下を保持する # ・群を構成する複数の個体 # ・個体を描画するcanvasとそのcontextのオブジェクト class Cluster # コンストラクタはcanvasオブジェクトを受け取り、そのcontextをプロパティに登録 constructor: (canvas) -> @creatures = [] @canvas = canvas @context = @canvas.getContext("2d") if @canvas.getContext return # すべての個体オブジェクトを群から削除する clearCreatures: -> @creatures = [] # 引数で与えられた個体を群に加える addCreature: (creature) -> @creatures.push(creature) return # 群の全個体を描画する # 描画エリアをクリアした後、群の全個体のdraw()メソッドを呼び出している draw: -> @context.fillStyle = "#e0e0e0" @context.fillRect(0, 0, @canvas.width, @canvas.height) for creature in @creatures creature.draw(@context) return # 秒単位のインターバル受け取り、すべての個体を移動させる move: (interval_sec) -> for creature in @creatures creature.move(interval_sec) return # Boidsアプリケーションの動作を統括するクラス # 唯一、ブラウザから直接呼び出され、初期値の設定やClusterの操作を担当 class Controler # Boids 1.2からstaticなクラスプロパティとした @interval_sec: 1 # animation frame interval in seconds constructor: (canvas) -> # Boidsの活動領域 @field = new Field(canvas) # 群のオブジェクト @cluster = new Cluster(canvas) # 個体の数 @population = 0 # アニメーション用のフラグとtimer @isRunning = false @timer = undefined # 定数として扱うクラス変数を設定するメソッド群 setVMax: (vMax) -> Creature._vMax = vMax setCreatureSize: (size) -> Creature._drawSize = size setWallDetectionLength: (pix) -> Creature._wallDetectionLength = pix setWallRepulsionParam: (param) -> Creature._wallRepulsionParam = param setIntervalSec: (@interval_sec) -> # 一つの個体を発生させるメソッド # 位置はcanvasに収まるよう、速度はvMax/2~vMaxの範囲でランダム createCreature: -> cr = new Creature(@cluster, @field) x = Math.floor(Math.random() * @field.xMax) y = Math.floor(Math.random() * @field.yMax) v = (Math.random() + 1) * Creature._vMax / 2 a = Math.random() * Math.PI * 2 cr.setPosition(x, y) cr.setVelocity(v * Math.cos(a), v * Math.sin(a)) return cr # 引数で指定された数の個体を発生させ、Clusterオブジェクトに登録する generate: (@population) -> @cluster.clearCreatures() for i in [1..@population] cr = @createCreature() @cluster.addCreature(cr) @cluster.draw() # アニメーションの開始/停止の切り替え # アニメーションはsetInterval()で実装している startAndStop: () -> if @isRunning clearInterval(@timer) @isRunning = false else @timer = setInterval => @cluster.move(@interval_sec) @cluster.draw() return , @interval_sec * 1000 @isRunning = true return # 外部からアクセス可能な名前の設定 Boids20.Controler = Controler # スクリプトが読み込まれた時の実行部分 jQuery ($) -> # Controlerを作成し、canvasオブジェクトを渡す canvas = $("#Boids20_canvas")[0] controler = new Boids20.Controler(canvas) # 固定パラメータのセット # パラメータの値は、Javascript形式でHTMLに埋め込まれている想定 controler.setVMax(BD20_V_MAX) controler.setCreatureSize(BD20_CREATURE_SIZE) controler.setWallDetectionLength(BD20_WALL_DETECTION_LENGTH) controler.setWallRepulsionParam(BD20_WALL_REPULSION_PARAM) controler.setIntervalSec(BD20_INTERVAL_SEC) # Generateボタンが押されたとき、指定された個体数で群をつくる $("#boids20_generate").click -> pop = $("#boids20_population").val() if pop < 1 pop = 1 $("#boids20_population").val(pop) controler.generate(pop) # Start/Stopボタンが押されたとき、アニメーションの動作を切り替える $("#boids20_start_and_stop").click -> controler.startAndStop() |