概要
ネイピア数(Napier’s constant)は自然対数の底で、一般に記号で表される。オイラー数(Euler’s number)と呼ばれることもある。
(1)
定義
ヤコブ・ベルヌーイによる定義。
(2)
性質
(3)
(4)
(5)
(6)
テイラー展開
(7)
ネイピア数(Napier’s constant)は自然対数の底で、一般に記号で表される。オイラー数(Euler’s number)と呼ばれることもある。
(1)
ヤコブ・ベルヌーイによる定義。
(2)
(3)
(4)
(5)
(6)
(7)
Boids 3.1で、いよいよEnemyを導入する。大まかな流れは以下の通り。
Creature
クラスを継承したEnemyCreature
クラスを定義するCreature
クラスを継承したEnemyCreature
クラスを追加する。そのメソッド構成は以下の通り。specificBehaviorについては、Enemyに特別な行動ルールはなく、分離(separation)のみ実装している。separation()
メソッドはBoidCreature
とまったく同じロジック。
1 2 3 4 5 6 7 8 9 10 11 |
class EnemyCreature extends Creature constructor: (cluster, field) -> draw: (ctx) -> specificBehavior: (intervalInSec) -> @separation() .... separation: -> .... |
Controler
クラスのパラメータ設定用setterを追加(後述)
BoidCreature
クラスと同じ内容で、createEnemyCreature()
メソッドを追加
generate()
メソッドを変更generate()
メソッドで、Boidに加えてEnemyも指定個体数だけ発生させるよう追加。
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 |
class Controler .... createBoidCreature: -> .... createEnemyCreature: -> cr = new EnemyCreature(@enemyCluster, @field) x = Math.floor(Math.random() * @field.xMax) y = Math.floor(Math.random() * @field.yMax) v = Math.random() * (EnemyCreature.vMax - EnemyCreature.vMin) + EnemyCreature.vMin ang = Math.random() * Math.PI * 2 cr.setPosition(x, y) cr.setVelocity(v * Math.cos(ang), v * Math.sin(ang)) return cr generate: (boidPopulation, enemyPopulation) -> @enemyCluster.clearCreatures() if enemyPopulation > 0 for i in [1..enemyPopulation] cr = @createEnemyCreature() @enemyCluster.addCreature(cr) @boidCluster.clearCreatures() for i in [1..boidPopulation] cr = @createBoidCreature() @boidCluster.addCreature(cr) # 個体発生後に画面をクリアして描画 @context.fillStyle = "#000080" @context.fillRect(0, 0, @canvas.width, @canvas.height) @boidCluster.draw(@context) @enemyCluster.draw(@context) .... |
BoidCreature
クラスに新たにavoidEnemy()
メソッドを追加し、specificBehavior()
でこれを呼び出すよう変更。avoidEnemy()
メソッドの内容は以下の通り。
sepration()
メソッドと同じ
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 |
class BoidCreature extends Creature .... specificBehavior: (intervalInSec) -> @separation() @cohesion() @alignment() @avoidEnemy() return .... avoidEnemy: -> # 回避行動の方向転換計算用cos/sin cs = Math.cos(BoidCreature.antiEnemyAngle) sn = Math.sin(BoidCreature.antiEnemyAngle) # 自身に最も近いEnemyの個体 @nearestCreature = null # Enemyのすべての個体についてサーチ for enemy in @enemyCluster.creatures # その個体が自分の前方にいるなら if enemy.isInFrontOf(this) d = Vector.distance(this.pos, enemy.pos) # その個体が視野の中にいるなら if d <= BoidCreature.antiEnemyFieldDepth # 既に個体がセットされていて、今回の個体の方が近ければ入れ替え # 最初に認識した個体ならそのままセット if @nearestEnemy? if d <= Vector.distance(@nearestEnemy.pos, this.pos) @nearestEnemy = enemy else @nearestEnemy = enemy # 近くの個体が視野内に存在するなら回避計算 if @nearestEnemy? # Enemyが自分の右にいるなら左へ、自分の左にいるなら右へ向きを変更 if @nearestEnemy.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 |
ページ読み込み時の実行部分は以下の通りで、2つのボタンのクリックに対する処理を定義。
BoidCreature
の群とEnemyCreature
の群を発生させるControler
クラスのstartAndStop()
メソッドを呼び出す
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 |
# Generateボタンが押されたとき、指定された数でBoidとEnemyの群をつくる # Boids3.1では、VMax = VMinとして、全個体が同じ速さを持つようにしている $("#bd31_generate").click -> v = $("#bd31_boid_velocity").val() v = Math.max(v, BD31_BOID_VMIN) v = Math.min(v, BD31_BOID_VMAX) $("#bd31_boid_velocity").val(v) controler.setBoidVMax(v) controler.setBoidVMin(v) boidPop = $("#bd31_boid_population").val() boidPop = Math.max(boidPop, BD31_BOID_POPULATION_MIN) boidPop = Math.min(boidPop, BD31_BOID_POPULATION_MAX) $("#bd31_boid_population").val(boidPop) v = $("#bd31_enemy_velocity").val() v = Math.max(v, BD31_ENEMY_VMIN) v = Math.min(v, BD31_ENEMY_VMAX) $("#bd31_enemy_velocity").val(v) controler.setEnemyVMax(v) controler.setEnemyVMin(v) enemyPop = $("#bd31_enemy_population").val() enemyPop = Math.max(enemyPop, BD31_ENEMY_POPULATION_MIN) enemyPop = Math.min(enemyPop, BD31_ENEMY_POPULATION_MAX) $("#bd31_enemy_population").val(enemyPop) controler.generate(boidPop, enemyPop) # Start/Stopボタンが押されたとき、アニメーションの動作を切り替える $("#bd31_start_and_stop").click -> controler.startAndStop() |
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 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>Boids 3.1/CoffeeScript</title> <script type="text/javascript" src="../lib/jquery.js"></script> <script type="text/javascript" src="../lib/taustation_geom2d.js"></script> </head> <body> <h2>Boids 3.1</h2> <script> //<![CDATA[ BD31_WALL_DETECTION_LENGTH = 100; BD31_WALL_REPULSION_PARAM = 16; BD31_BOID_VMAX = 200; BD31_BOID_VMIN = 50; BD31_BOID_SIZE = 10; BD31_BOID_COLOR = "#FFFFFF"; BD31_BOID_POPULATION_MIN = 1; BD31_BOID_POPULATION_MAX = 300; BD31_BOID_SEPARATION_FIELD_DEPTH = 50; BD31_BOID_SEPARATION_ANGLE = 12 * Math.PI / 180; BD31_COHESION_ANGLE = 5 * Math.PI / 180; BD31_ALIGNMENT_FIELD_DEPTH = 250; BD31_ALIGNMENT_ANGLE = 5 * Math.PI / 180; BD31_ANTI_ENEMY_FIELD_DEPTH = 50; BD31_ANTI_ENEMY_ANGLE = 30 * Math.PI / 180; BD31_ENEMY_VMAX = 200; BD31_ENEMY_VMIN = 50; BD31_ENEMY_SIZE = 15; BD31_ENEMY_COLOR = "#FF0000"; BD31_ENEMY_POPULATION_MIN = 0; BD31_ENEMY_POPULATION_MAX = 5; BD31_ENEMY_SEPARATION_FIELD_DEPTH = 50; BD31_ENEMY_SEPARATION_ANGLE = 10 * Math.PI / 180; BD31_INTERVAL_SEC = 0.05; //]]> </script> <!-- Boids3.1の入力表示部 --> <p> <label><code>個体数 :</code></label><input type="number" id="bd31_boid_population" value="100" min="1" max="300" /><br /> <label><code>個体の速さ:</code></label><input type="number" id="bd31_boid_velocity" value="100" min="50" max="200" /><br /> <label><code>敵の数 :</code></label><input type="number" id="bd31_enemy_population" value="0" min="0" max="5" /><br /> <label><code>敵の速さ :</code></label><input type="number" id="bd31_enemy_velocity" value="75" min="50" max="200" /><br /> <input type="button" id="bd31_generate" value="Generate" /> <input type="button" id="bd31_start_and_stop" value="Start/Stop" /> </p> <canvas id="bd31_canvas" width="600" height="400" style="background-color: #000080"> </canvas> <script type="text/javascript" src="../coffee/boids/boids_3_1.js"></script> </body> </html> |
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 |
Boids31 = {} # パッケージBoids30の本体 do -> # 独自クラスのエイリアス # パッケージtaustation_geom2d.jsが必要。 Vector = taustation_geom2d.Vector MovingAgent = taustation_geom2d.MovingAgent # Boidsが活動する空間のクラス # このバージョンでは長方形 class Field # Field内の壁を避けながら移動する一般化Creeatureクラス # Boids3.0で、それまでのCreatureクラスをCreatureとBoidCreatureに分けて継承させた # Boidsの行動ルールは、このクラスを継承したBoidsCreatureクラスで実装する class Creature extends MovingAgent # Boidの個体を実現するクラス # Boids3.0で、Creatureクラスを継承するように変更 # 敵を回避するためのプロパティやメソッドを追加 class BoidCreature extends Creature # Boids3.1で導入 # Boidsに対する「敵」を表すクラス class EnemyCreature extends Creature # BoidsとEnemyの群を管理するクラスで、以下を保持する # - 群を構成する複数の個体 # - 個体を描画するcanvasのcontextオブジェクト class Cluster # Boidsアプリケーションの動作を統括するクラス # 唯一、ブラウザから直接呼び出され、初期値の設定やClusterの操作を担当 class Controler # 外部からアクセス可能な名前の設定 Boids31.Controler = Controler # スクリプトが読み込まれた時の実行部分 jQuery ($) -> # Controlerを作成し、canvasオブジェクトを渡す canvas = $("#bd31_canvas")[0] context = canvas.getContext("2d") if canvas.getContext controler = new Boids31.Controler(canvas, context) # 固定パラメータのセット # パラメータの値は、Javascript形式でHTMLに埋め込まれている想定 controler.setWallDetectionLength(BD31_WALL_DETECTION_LENGTH) controler.setWallRepulsionParam(BD31_WALL_REPULSION_PARAM) controler.setBoidSize(BD31_BOID_SIZE) controler.setBoidColor(BD31_BOID_COLOR) controler.setBoidSeparationFieldDepth(BD31_BOID_SEPARATION_FIELD_DEPTH) controler.setBoidSeparationAngle(BD31_BOID_SEPARATION_ANGLE) controler.setCohesionAngle(BD31_COHESION_ANGLE) controler.setAlignmentFieldDepth(BD31_ALIGNMENT_FIELD_DEPTH) controler.setAlignmentAngle(BD31_ALIGNMENT_ANGLE) controler.setAntiEnemyFieldDepth(BD31_ANTI_ENEMY_FIELD_DEPTH) controler.setAntiEnemyAngle(BD31_ANTI_ENEMY_ANGLE) controler.setEnemySize(BD31_ENEMY_SIZE) controler.setEnemyColor(BD31_ENEMY_COLOR) controler.setEnemySeparationFieldDepth(BD31_ENEMY_SEPARATION_FIELD_DEPTH) controler.setEnemySeparationAngle(BD31_ENEMY_SEPARATION_ANGLE) controler.setIntervalSec(BD31_INTERVAL_SEC) # Generateボタンが押されたとき、指定された数でBoidとEnemyの群をつくる # Boids3.1では、VMax = VMinとして、全個体が同じ速さを持つようにしている $("#bd31_generate").click -> v = $("#bd31_boid_velocity").val() v = Math.max(v, BD31_BOID_VMIN) v = Math.min(v, BD31_BOID_VMAX) $("#bd31_boid_velocity").val(v) controler.setBoidVMax(v) controler.setBoidVMin(v) boidPop = $("#bd31_boid_population").val() boidPop = Math.max(boidPop, BD31_BOID_POPULATION_MIN) boidPop = Math.min(boidPop, BD31_BOID_POPULATION_MAX) $("#bd31_boid_population").val(boidPop) v = $("#bd31_enemy_velocity").val() v = Math.max(v, BD31_ENEMY_VMIN) v = Math.min(v, BD31_ENEMY_VMAX) $("#bd31_enemy_velocity").val(v) controler.setEnemyVMax(v) controler.setEnemyVMin(v) enemyPop = $("#bd31_enemy_population").val() enemyPop = Math.max(enemyPop, BD31_ENEMY_POPULATION_MIN) enemyPop = Math.min(enemyPop, BD31_ENEMY_POPULATION_MAX) $("#bd31_enemy_population").val(enemyPop) controler.generate(boidPop, enemyPop) # Start/Stopボタンが押されたとき、アニメーションの動作を切り替える $("#bd31_start_and_stop").click -> controler.startAndStop() |
1 2 3 4 5 6 7 8 |
# Boidsが活動する空間のクラス # このバージョンでは長方形 class Field constructor: (width, height) -> @xMin = 0 @yMin = 0 @xMax = width @yMax = height |
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 |
# Field内の壁を避けながら移動する一般化Creeatureクラス # Boids3.0で、それまでのCreatureクラスをCreatureとBoidCreatureに分けて継承させた # Boidsの行動ルールは、このクラスを継承したBoidsCreatureクラスで実装する class Creature extends MovingAgent # 壁の衝突回避を認識する距離 @wallDetectionLength: 100 # 反発力パラメータ @wallRepulsionParam: 4 constructor: (field) -> super() # 加速度ベクトル @a = new Vector(0, 0) @field = field # 個体を動かすメソッド # 群を表すClusterクラスのmove()メソッドから呼ばれる move: (intervalInSec) -> # 壁を避ける @wallAvoidance(intervalInSec) # このクラスを継承した具象クラスでの行動は、このメソッドをオーバーライド @specificBehavior(intervalInSec) # 最終的な位置を決定 @determinePosition(intervalInSec) # 壁を避ける行動 # 壁からの距離に応じた斥力を(repulsion)想定し、x/y方向の加速度を計算 wallAvoidance: (intervalInSec) -> 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 # 加速度を考慮した速度の変化を計算 # 速度の絶対値が増え続けないようにキャップで抑える # 速度成分の符号を得るのに、Math.signがサポートされていないブラウザが多いため、 # 値/絶対値で符号を得ている @v.plusEq(@a.times intervalInSec) @v.x = @v.x / Math.abs(@v.x) * Math.min(Math.abs(@v.x), BoidCreature.vMax) @v.y = @v.y / Math.abs(@v.y) * Math.min(Math.abs(@v.y), BoidCreature.vMax) # このクラスを継承するサブクラスで、このメソッドをオーバーライドする specificBehavior: (intervalInSec) -> console.log("specificBehavior() of Creature class is abstract.") # Creatureクラスの現在位置と速度から、次の時間ステップでの位置を決定する determinePosition: (intervalInSec) -> # インターバルの間の移動量を計算 @pos.plusEq(@v.times intervalInSec) # 壁にぶつかった場合は全反射 if @pos.x <= @field.xMin @pos.x = @field.xMin * 2 - @pos.x @v.x = -@v.x else if @pos.x >= @field.xMax @pos.x = @field.xMax * 2 - @pos.x @v.x = -@v.x if @pos.y <= @field.yMin @pos.y = @field.yMin * 2 - @pos.y @v.y = -@v.y else if @pos.y >= @field.yMax @pos.y = @field.yMax * 2 - @pos.y @v.y = -@v.y |
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 |
# Boidの個体を実現するクラス # Boids3.0で、Creatureクラスを継承するように変更 # 敵を回避するためのプロパティやメソッドを追加 class BoidCreature extends Creature # private staticなクラス・プロパティ @vMax: 0 @vMin: 0 # 描画の基本となるサイズと色 @drawSize: 8 @color: "#000000" # 視野の深さと分離行動の回避角 @separationFieldDepth: 100 @separationAngle: 0 # 結合行動の変化角 @cohesionAngle: 0 # 整列高度の視野の長さと変化角 @alignmentFieldDepth: 100 @alignmentAngle: 0 # 敵を回避する視野の深さと変化角 @antiEnemyFieldDepth: 100 @antiEnemyAngle: 0 # 自分が属する群と敵の群への参照を保持する constructor: (boidCluster, enemyCluster, field) -> super(field) @boidCluster = boidCluster @enemyCluster = enemyCluster @nearestCreature = null # 個体を描画するメソッド # canvasのコンテキストを引数で受け取る draw: (ctx) -> v = @v.abs() sx = BoidCreature.drawSize * @v.x / v sy = BoidCreature.drawSize * @v.y / v p = 0.25 ctx.fillStyle = BoidCreature.color 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()メソッドから呼ばれる # intervalInSecは秒単位のアニメーション・フレームのインターバル specificBehavior: (intervalInSec) -> # 分離行動(separation) @separation() # 結合行動(cohesion) @cohesion() # 整列行動(alignment) @alignment() # 敵回避 @avoidEnemy() return # 分離行動(separation) # 前方視角180度内で一定距離内の視野の中で、最も近い個体を避けるよう方向を変える # 相手方の速度の向きには関係なく、相手側と反対方向に速度の向きをを少し変更する。 separation: -> # 回避行動の方向転換計算用cos/sin cs = Math.cos(BoidCreature.separationAngle) sn = Math.sin(BoidCreature.separationAngle) # 自身に最も近いBoidとEnemyの個体 @nearestCreature = null @nearestEnemy = null # 自分以外の群のすべての個体についてサーチ for other in @boidCluster.creatures if other != this # その個体が自分の前方にいるなら if other.isInFrontOf(this) d = Vector.distance(this.pos, other.pos) # その個体が視野の中にいるなら if d <= BoidCreature.separationFieldDepth # 既に個体がセットされていて、今回の個体の方が近ければ入れ替え # 最初に認識した個体ならそのままセット 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 # 結合行動(cohesion) # 前方視角180度以内の全個体の重心に向かって少し方向を変える cohesion: -> # 群のうち認識した個体の数とそのグループの重心 n = 0 clusterCenter = null # 結合行動の方向転換計算用cos/sin cs = Math.cos(BoidCreature.cohesionAngle) sn = Math.sin(BoidCreature.cohesionAngle) # 自分以外の群のすべての個体についてサーチ for other in @boidCluster.creatures if other != this # 相手が自身の前方にいるなら、その位置を考慮 if other.isInFrontOf(this) n++ if clusterCenter? clusterCenter.plusEq(other.pos) else clusterCenter = new Vector(other.pos.x, other.pos.y) # 前方の相手が1つでも存在すれば、結合行動をとる if clusterCenter? clusterCenter.divEq(n) # 重心が自分の右にあれば左に、自分の左にあれば右に進路を変更 if clusterCenter.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 # 整列行動(alignment) # 分離行動と同じ視野内の個体群の平均速度に合わせるよう少し方向を変える alignment: -> vMean = null n = 0 # 整列行動の方向転換計算用cos/sin cs = Math.cos(BoidCreature.alignmentAngle) sn = Math.sin(BoidCreature.alignmentAngle) # 自分以外の群のすべての個体についてサーチ for other in @boidCluster.creatures if other != this # その個体が自分の前方にいるなら if other.isInFrontOf(this) d = Vector.distance(this.pos, other.pos) # その個体が視野の中にいるなら if d <= BoidCreature.alignmentFieldDepth # 既に個体が視野内にあれば平均速度の計算を続行 n++ if vMean? vMean.plusEq(other.v) else vMean = new Vector(0, 0) # 視野内に個体がいるなら速度変更の計算へ if vMean? vMean.divEq(n) if vMean.isHeadingToRightOf(@v) 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 # Boids3.1で追加 # 敵を回避する行動で、分離行動と同じロジック avoidEnemy: -> # 回避行動の方向転換計算用cos/sin cs = Math.cos(BoidCreature.antiEnemyAngle) sn = Math.sin(BoidCreature.antiEnemyAngle) # 自身に最も近いEnemyの個体 @nearestCreature = null # Enemyのすべての個体についてサーチ for enemy in @enemyCluster.creatures # その個体が自分の前方にいるなら if enemy.isInFrontOf(this) d = Vector.distance(this.pos, enemy.pos) # その個体が視野の中にいるなら if d <= BoidCreature.antiEnemyFieldDepth # 既に個体がセットされていて、今回の個体の方が近ければ入れ替え # 最初に認識した個体ならそのままセット if @nearestEnemy? if d <= Vector.distance(@nearestEnemy.pos, this.pos) @nearestEnemy = enemy else @nearestEnemy = enemy # 近くの個体が視野内に存在するなら回避計算 if @nearestEnemy? # Enemyが自分の右にいるなら左へ、自分の左にいるなら右へ向きを変更 if @nearestEnemy.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 |
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 |
# Boids3.1で導入 # Boidsに対する「敵」を表すクラス class EnemyCreature extends Creature # private staticなクラス・プロパティ @vMax: 0 @vMin: 0 # 描画の基本となるサイズと色 @drawSize: 8 @color: "#000000" # 視野の長さ(奥行)と変化角 @separationFieldDepth: 100 @separationAngle: 0 # 自分が属する群への参照を保持する constructor: (cluster, field) -> super(field) @enemyCluster = cluster @nearestCreature = null # 個体を描画するメソッド # canvasのコンテキストを引数で受け取る draw: (ctx) -> v = @v.abs() sx = EnemyCreature.drawSize * @v.x / v sy = EnemyCreature.drawSize * @v.y / v p = 0.25 ctx.fillStyle = EnemyCreature.color 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()メソッドから呼ばれる # intervalInSecは秒単位のアニメーション・フレームのインターバル specificBehavior: (intervalInSec) -> # Enemy同士の分離行動 @separation() # 分離行動(sepration) # BoidCreatureと同じ内容 # EnemyCreatureとの衝突のみ避ける separation: -> # 回避行動の方向転換計算用cos/sin cs = Math.cos(EnemyCreature.separationAngle) sn = Math.sin(EnemyCreature.separationAngle) # 自身に最も近い個体 @nearestCreature = null # 自分以外の群のすべての個体についてサーチ for other in @enemyCluster.creatures if other != this # その個体が自分の前方にいるなら if other.isInFrontOf(this) d = Vector.distance(this.pos, other.pos) # その個体が視野の中にいるなら if d <= EnemyCreature.separationFieldDepth # 既に個体がセットされていて、今回の個体の方が近ければ入れ替え # 最初に認識した個体ならそのままセット 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 |
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 |
# BoidsとEnemyの群を管理するクラスで、以下を保持する # - 群を構成する複数の個体 # - 個体を描画するcanvasのcontextオブジェクト class Cluster # canvasのcontextをプロパティに登録 constructor: (context) -> @context = context @creatures = [] return # すべての個体オブジェクトを群から削除する clearCreatures: -> @creatures = [] # 引数で与えられた個体を群に加える addCreature: (creature) -> @creatures.push(creature) return # 群の全個体を描画する # 群の全個体のdraw()メソッドを呼び出している # 画面のクリアはより上位の処理で行う draw: (context) -> for creature in @creatures creature.draw(context) return # 秒単位のインターバル受け取り、すべての個体を移動させる move: (intervalInSec) -> for creature in @creatures creature.move(intervalInSec) return |
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 |
# Boidsアプリケーションの動作を統括するクラス # 唯一、ブラウザから直接呼び出され、初期値の設定やClusterの操作を担当 class Controler # BoidとEnemyの発生個体数の最小値と最大値 constructor: (canvas, context) -> @canvas = canvas @context = context # Boidsの活動領域 @field = new Field(canvas.width, canvas.height) # Boidの群とEnemyの群のオブジェクト @boidCluster = new Cluster(context) @enemyCluster = new Cluster(context) # アニメーション用のフラグとtimer @isRunning = false @timer = undefined # 秒単位のアニメーション・フレーム間隔 @intervalInSec = 1 # animation frame interval in seconds # 定数として扱うクラス変数を設定するメソッド群 # 壁を避けるための認識距離と斥力係数 setWallDetectionLength: (pix) -> Creature.wallDetectionLength = pix setWallRepulsionParam: (param) -> Creature.wallRepulsionParam = param # Boid個体の実体に関するパラメータ setBoidVMax: (vMax) -> BoidCreature.vMax = vMax setBoidVMin: (vMin) -> BoidCreature.vMin = vMin setBoidSize: (size) -> BoidCreature.drawSize = size setBoidColor: (color) -> BoidCreature.color = color # Boid個体の分離・結合・整列に関するパラメータ setBoidSeparationFieldDepth: (length) -> BoidCreature.separationFieldDepth = length setBoidSeparationAngle: (angle) -> BoidCreature.separationAngle = angle setCohesionAngle: (angle) -> BoidCreature.cohesionAngle = angle setAlignmentFieldDepth: (length) -> BoidCreature.alignmentFieldDepth = length setAlignmentAngle: (angle) -> BoidCreature.alignmentAngle = angle # Boid個体が敵を避ける行動のパラメータ setAntiEnemyFieldDepth: (length) -> BoidCreature.antiEnemyFieldDepth = length setAntiEnemyAngle: (angle) -> BoidCreature.antiEnemyAngle = angle # Enemy個体の実体に関するパラメータ setEnemyVMax: (vMax) -> EnemyCreature.vMax = vMax setEnemyVMin: (vMin) -> EnemyCreature.vMin = vMin setEnemySize: (size) -> EnemyCreature.drawSize = size setEnemyColor: (color) -> EnemyCreature.color = color # Enemy個体の分離に関するパラメータ setEnemySeparationFieldDepth: (length) -> EnemyCreature.separationFieldDepth = length setEnemySeparationAngle: (angle) -> EnemyCreature.separationAngle = angle # アニメーションフレーム間隔 setIntervalSec: (@intervalInSec) -> # 一つのBoidの個体を発生させるメソッド # 位置はFieldに収まるよう、速度はvMax~vMinの範囲でランダム createBoidCreature: -> cr = new BoidCreature(@boidCluster, @enemyCluster, @field) x = Math.floor(Math.random() * @field.xMax) y = Math.floor(Math.random() * @field.yMax) v = Math.random() * (BoidCreature.vMax - BoidCreature.vMin) + BoidCreature.vMin ang = Math.random() * Math.PI * 2 cr.setPosition(x, y) cr.setVelocity(v * Math.cos(ang), v * Math.sin(ang)) return cr # Boids3.1で追加 # 一つの敵の個体を発生させるメソッド # 位置はFieldに収まるよう、速度はvMax~vMinの範囲でランダム createEnemyCreature: -> cr = new EnemyCreature(@enemyCluster, @field) x = Math.floor(Math.random() * @field.xMax) y = Math.floor(Math.random() * @field.yMax) v = Math.random() * (EnemyCreature.vMax - EnemyCreature.vMin) + EnemyCreature.vMin ang = Math.random() * Math.PI * 2 cr.setPosition(x, y) cr.setVelocity(v * Math.cos(ang), v * Math.sin(ang)) return cr # Boids3.1で変更 # 引数で指定された数のBoidとEnemyの個体を発生させ、Clusterオブジェクトに登録する generate: (boidPopulation, enemyPopulation) -> @enemyCluster.clearCreatures() if enemyPopulation > 0 for i in [1..enemyPopulation] cr = @createEnemyCreature() @enemyCluster.addCreature(cr) @boidCluster.clearCreatures() for i in [1..boidPopulation] cr = @createBoidCreature() @boidCluster.addCreature(cr) # 個体発生後に画面をクリアして描画 @context.fillStyle = "#000080" @context.fillRect(0, 0, @canvas.width, @canvas.height) @boidCluster.draw(@context) @enemyCluster.draw(@context) # Boids3.1でEnemyCreatureにも対応 # アニメーションの開始/停止の切り替え startAndStop: () -> if @isRunning clearInterval(@timer) @isRunning = false else @timer = setInterval => # 移動・再描画前に画面をクリア @context.fillStyle = "#000080" @context.fillRect(0, 0, @canvas.width, @canvas.height) @boidCluster.move(@intervalInSec) @boidCluster.draw(@context) @enemyCluster.move(@intervalInSec) @enemyCluster.draw(@context) return , @intervalInSec * 1000 @isRunning = true return |
Boids 3.xシリーズで敵(Enemy)を導入する準備として、クラス構成の改良などを行った。
Boids 2.xまではBoidだけだった生物の種類に、新たにEnemyという別の種類が加わるが、どちらについても以下の特徴は共通している。
Cluster
オブジェクトにまとめて扱われるそこで、これらの特徴に関する部分をCreature
クラスから抜き出したものを新たにCreature
としてテンプレート化し、Boid特有の動作についてはCreature
クラスを継承したBoidCreature
で実装した。
このほか、BoidとEnemy2つの群を扱うことから、Cluster
で処理していた画面の消去処理を外に出すなどの変更を行っている。
まず、元となったBoids 2.3のコードの構成を確認する。壁の回避、分離、結合、整列に関するメソッドを定義し、これらをmove()
メソッドで呼び出している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class Creature extends MovingAgent constructor: (cluster, field) -> draw: (ctx) -> move: (interval_sec) -> @wallAvoidance() @separation() @cohesion() @alignment() .... wallAvoidance: -> separation: -> cohesion: -> alignment: -> |
Boids 3.0では2.3のCreature
クラスをCreature
とBoidCreature
に分け、Creature
クラスにはBoidとEnemyに共通する動作を書き、Boidに特有の動作についてはBoidCreature
のメソッドで実装した。
Creature
クラスは以下のメソッドを持っている。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Creature extends MovingAgent constructor: (field) -> move: (intervalInSec) -> @wallAvoidance(intervalInSec) @specificBehavior(intervalInSec) @determinePosition(intervalInSec) wallAvoidance: (intervalInSec) -> specificBehavior: (intervalInSec) -> determinePosition: (intervalInSec) -> |
BoidCreature
クラスはCreature
クラスを継承し、specificBehavior
()メソッドを実装する。
BoidCreature
クラスで分離、結合、整列に関するメソッドを定義し、specificBehavior()
メソッドでそれらを呼び出している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class BoidCreature extends Creature constructor: (boidCluster, enemyCluster, field) -> draw: (ctx) -> specificBehavior: (intervalInSec) -> @separation() @cohesion() @alignment() separation: -> cohesion: -> alignment: -> |
Boid 3.xでは、BoidとEnemyという2つの生物をそれぞれ別のCluster
オブジェクトに登録して扱う。Boids 2.3までは、アニメーションフレーム再描画のための画面消去処理をCluster
クラスのdraw()
メソッドで行っていたが、このままだとBoid、Enemyそれぞれの描画の際に画像が消去されて、どちらか一方が画面に表示されなくなる。
1 2 3 4 5 6 7 8 9 |
class Cluster .... draw: -> @context.fillStyle = "#e0e0e0" @context.fillRect(0, 0, @canvas.width, @canvas.height) for creature in @creatures creature.draw(@context) return |
そこで、画面消去処理をCluster
ではなくControler
のgenerate()
メソッドとstartAndStop()
メソッドに置いた。
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 |
class Controler .... generate: (boidPopulation) -> @boidCluster.clearCreatures() for i in [1..boidPopulation] cr = @createBoidCreature() @boidCluster.addCreature(cr) # 個体発生後に画面をクリアして描画 @context.fillStyle = "#000080" @context.fillRect(0, 0, @canvas.width, @canvas.height) @boidCluster.draw(@context) # アニメーションの開始/停止の切り替え startAndStop: () -> if @isRunning clearInterval(@timer) @isRunning = false else @timer = setInterval => # 移動・再描画前に画面をクリア @context.fillStyle = "#000080" @context.fillRect(0, 0, @canvas.width, @canvas.height) @boidCluster.move(@intervalInSec) @boidCluster.draw(@context) return , @intervalInSec * 1000 @isRunning = true return |
Boids 3.xでは、群として行動している集団に「敵(Enemy)」を投入する。BoidがEnemyを避けるように行動ルールを加え、群としてどのような行動パターンとなるかを確認する。
敵を導入する準備として、Creature
クラスの抽象化・テンプレート化などの大幅な改定を行った。挙動はBoids 2.3と変わらない。
このバージョンで「敵(Enemy)」を導入する。
デモンストレーションのBoidsの様子は、あたかも小魚が天敵の魚を避けるような行動パターンをとっている。BoidがEnemyを回避するのはEnemyが前方にいるときだけだが、群としての回避パターンはよく再現できている。
WordPressでカテゴリをクリックしても「ページが見つかりませんでした」と表示される現象が生じた。
ネット上で同様の現象が挙げられており、「タグやカテゴリをクリックすると404エラーで表示されない」というものが多かった。それらのうち以下の方法で解決したので記録しておく。
他にもパーマリンクの設定を保存しなおすだけで直る、というのもあったが、こちらは効かなかった。
ポイント
親クラスを継承した子クラスのコンストラクタでは、基本的にsuper()
で親クラスのコンストラクタを呼び出す。
たとえば次のように親クラスParent
を子クラスChild
が継承した場合、子クラスに親クラスのプロパティが引き継がれている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Parent constructor: -> @name = "parent" class Child extends Parent ins = new Parent() console.log(ins.name) ins = new Child() console.log(ins.name) # 実行結果 # parent # parent |
ところが次のように子クラスでコンストラクタを定義した場合は、親クラスのプロパティが引き継がれない。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class Parent constructor: -> @name = "parent" class Child extends Parent constructor: -> ins = new Parent() console.log(ins.name) ins = new Child() console.log(ins.name) # 実行結果 # parent # undefined |
これは子クラスのコンストラクタが親クラスのコンストラクタをオーバーライドしてしまったためで、この状態では親クラスのコンストラクタが実行されない。
そこで、子クラスのコンストラクタからsupper()
で親クラスのコンストラクタを呼び出すと、親クラスのコンストラクタも実行され、プロパティが引き継がれる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class Parent constructor: -> @name = "parent" class Child extends Parent constructor: -> super() ins = new Parent() console.log(ins.name) ins = new Child() console.log(ins.name) # 実行結果 # parent # parent |
コンストラクタが引数をとる場合も、super()
で親クラスのコンストラクタに合わせた引数を指定すればよい。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class Parent constructor: (@name)-> class Child extends Parent constructor: (name)-> super(name) ins = new Parent("parent") console.log(ins.name) ins = new Child("child") console.log(ins.name) # 実行結果 # parent # child |
例えば次のコードでは、親クラスのmethod()
を子クラスのmethod()
がオーバーライドしている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Parent method: -> console.log("in Parent") class Child extends Parent method: -> console.log("in Child") ins = new Parent() ins.method() ins = new Child() ins.method() # 実行結果 # in Parent # in Child |
子クラスからオーバーライドした親クラスのメソッドを呼び出したい場合は、super()
を使う。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Parent method: -> console.log("in Parent") class Child extends Parent method: -> super() console.log("in Child") ins = new Child() ins.method() # 実行結果 # in Parent # in Child |
Javaの場合だと、抽象クラスを書く場合には”abstract”宣言をして、具体のクラスで実装すべきメソッドの宣言だけを行うが、CoffeeScript/Javascriptの場合はこれと異なる。
まず、同じ名前execution
のメソッドを持つ2つのクラスConcrete1
とConcrete2
を考える。メソッド名は同じだが、各クラスで処理内容は異なる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class Concrete1 execution: -> console.log("execution in Concrete 1") class Concrete2 execution: -> console.log("execution in Concrete 2") ins = new Concrete1() ins.execution() ins = new Concrete2() ins.execution() # 実行結果 # execution in Concrete 1 # execution in Concrete 2 |
次にexecution()
について共通な2つのクラスを抽象化したAbstract
クラスを導入する。
CoffeeScript/Javascriptではabstract
キーワードがなく、abstractなメソッドも中身が空のもので定義。ただし抽象クラスのままでインスタンス化したときの注意喚起のメッセージを実装。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class Abstract execution: -> console.log("execution of Abstrac must be overridden") class Concrete1 execution: -> console.log("execution in Concrete 1") class Concrete2 execution: -> console.log("execution in Concrete 2") ins = new Abstract() ins.execution() ins = new Concrete1() ins.execution() ins = new Concrete2() ins.execution() # 実行結果 # execution of Abstrac must be overridden # execution in Concrete 1 # execution in Concrete 2 |
Javaなどでは型指定が厳格なので、異なる2つのクラスをAbstract
クラスで抽象化して、同じメソッドを呼び出すことでクラスごとに特有の処理をさせる。
CoffeeScript/Javascriptでは変数の型がないため、抽象化をしなくてもメソッド名を同じにしておくだけで同じ効果を得る。ただしコンパイル時のチェックなどがない点に注意。
以下のように、2つのクラスで共通の処理と個別の処理が混在している場合を考える。
共通の処理を各クラスごとに実装しているが、コピー/ペースト操作で行うとしてもエラーが入り込みやすく、その後の保守性も悪い。
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 |
class Concrete1 execution: -> @commonExecution() @specificExecution() commonExecution: -> console.log("common execution") specificExecution: -> console.log("execution for Concrete 1") class Concrete2 execution: -> @commonExecution() @specificExecution() commonExecution: -> console.log("opening execution") specificExecution: -> console.log("execution for Concrete 2") ins = new Concrete1() ins.execution() ins = new Concrete2() ins.execution() # 実行結果 # common execution # execution for Concrete 1 # opening execution # execution for Concrete 2 |
エラー回避と保守性の向上のため、共通部分を一つにまとめる。
Abstract
を導入Abstract
にexecution()
メソッドのフレームを集約commonExecution()
をAbstract
のメソッドとして実装Abstract
を継承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class Abstract execution: -> @commonExecution() @specificExecution() commonExecution: -> console.log("common execution") class Concrete1 extends Abstract specificExecution: -> console.log("execution for Concrete 1") class Concrete2 extends Abstract specificExecution: -> console.log("execution for Concrete 2") ins = new Concrete1() ins.execution() ins = new Concrete2() ins.execution() # 実行結果 # common execution # execution for Concrete 1 # opening execution # execution for Concrete 2 |
Concrete1
とConcrete2
がAbstract
を継承したことによって2つの具象クラスがAbstract
クラスのexecute()
メソッドも継承され、具象クラスでexecute()
メソッドを呼ぶと、Abstract
のexecute()
メソッドが実行される。
その後、共通部分のcommonExecution()
はAbstract
のメソッドが呼ばれ、specificExecution()
については、各具象クラスでオーバーライドされたメソッドが呼ばれる。
各具象クラスでexecution()
のから書いていくこともできるが、この場合は共通処理が一括してsuper()で実行されるため、「共通の前処理と後処理の間に各クラス独自の処理を置きたい」という場合には適さない。
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 |
class Abstract execution: -> @commonExecution() @specificExecution() commonExecution: -> console.log("common execution") class Concrete1 extends Abstract execution: -> super() @specificExecution specificExecution: -> console.log("execution for Concrete 1") class Concrete2 extends Abstract execution: -> super() @specificExecution specificExecution: -> console.log("execution for Concrete 2") ins = new Concrete1() ins.execution() ins = new Concrete2() ins.execution() # 実行結果 # common execution # execution for Concrete 1 # opening execution # execution for Concrete 2 |
(1)
まず、以下のような図を考える。
ABの長さを余弦定理で表す。
(2)
これより以下を得るが、これは加法定理のうちの一つに対応している。
(3)
なお上式は、cosの性質からαとβの大小関係に関わらず成り立つ。
式(3)でβ → – βと置くと以下を得る。
(4)
式(3)でα → α+π/2と置くと以下を得る。
(5)
また上式においてβ → – βと置くと以下を得る。
(6)
これで加法定理に関する4つの式が得られた。
αやβが第3象限、第4象限にあるときは、それらからπ/2、πを減じて冒頭の図に対応させ、式変形をすることで同じ解を得る。
例えば下図のように、αが第3象限、βが第1象限にあるケースを考える。
ここでα = α’ + π/2と置けば、α’について加法定理が成り立つことが分かっているので、
(7)
αとα’の関係より、
(8)
これらより、以下の下方定理がこのケースについて成り立つことが示される。
(9)
三角関数のcosとsinは位相が90度ずれている。
これより、互いの関係は以下の通り。