// 群れ(flock)のシミュレーション package { import flash.display.Sprite; import flash.events.Event; import flash.geom.Matrix; import flash.geom.Point; /** 群れのシミュレーション */ [SWF(width="465", height="465", backgroundColor="0x00FFFF", frameRate="30")] public class Flock extends Sprite { public static const W:int = 465; public static const H:int = 465; public static const N:int = 8*2; /** Array of Individual */ private var crowd:Array = []; private var m2v:Matrix; private var v2m:Matrix; public function Flock() { m2v = new Matrix(); m2v.scale(4, 4); m2v.translate(W/2, H/2); v2m = m2v.clone(); v2m.invert(); for (var i:int = 0; i < N; ++i) { crowd.push(new Individual(new Point(Math.random() * 10.0 - 5.0, Math.random() * 10.0 - 5.0), Math.random() * 3 + 3, Math.random() * Math.PI / 2.0)); } addEventListener(Event.ENTER_FRAME, step); } /** * @param ev enterFrameイベント */ public function step(ev:Event):void { var esc:Point = v2m.transformPoint(new Point(mouseX, mouseY)); // マウスを避ける var c:Point = center(); // 重心 var a:Array = crowd.concat(); // 複製を作る while (a.length > 0) { var i1:Individual = a.shift(); // 取り出す var i2:Individual = nearest(a, i1.position); i1.step(c, i2, esc); i2.step(c, i1, esc); a.splice(a.indexOf(i2), 1); // 取り除く } repaint(); } /** 重心を得る * @return 重心 */ private function center():Point { var p:Point = new Point(); for each (var b:Individual in crowd) { p = p.add(b.position); } p.x /= N; p.y /= N; return p; } /** 一番近いやつを得る * @param a 個体集合 * @param p 検査点 * @return pに一番近い個体(無いときはnull) */ private static function nearest(a:Array, p:Point):Individual { var i:Individual = null; var min:Number = Number.MAX_VALUE; for each (var j:Individual in a) { var d:Number = j.position.subtract(p).length; if (d < min) { min = d; i = j; } } return i; } /** 再描画 */ private function repaint():void { graphics.clear(); for each (var i:Individual in crowd) { var p:Point = m2v.transformPoint(i.position); FishPainter.paint(graphics, p, i.orientation, i.state); } } } } import flash.geom.Point; /** 個体 */ class Individual { /** 最適距離 */ public static const SUITABLE:Number = 10.0; /** 加速度 */ public static const ACCELERATION:Number = 1.05; /** 位置 */ public var position:Point; /** 向き(単位ベクトル) */ public var orientation:Point; /** 速度 */ public var speed:Number; /** 状態 */ public var state:int; /** 状態を進めるカウント */ private var count:int; /** 個体の構築 * @param p 位置 * @param s 速度 * @param t 向きを表す角度(radian) */ public function Individual(p:Point, s:Number, t:Number) { this.position = p; this.orientation = new Point(Math.cos(t), Math.sin(t)); this.speed = s; } /** * @param c 群れの重心 * @param i 一番近い個体 * @param esc 避ける点 */ public function step(c:Point, i:Individual, esc:Point):void { // 避ける点に近いときは逃げる if (Point.distance(position, esc) < SUITABLE) { orientation = position.subtract(esc); orientation.normalize(1.0); speed = 10; } // この個体を原点とする一番近い個体の位置 var v:Point = i.position.subtract(position); // 一番近い個体との距離 var d:Number = v.length; // 一番近くの個体が前にいるか後ろにいるか調べて、一番近くの個体に速度を合わせる v = v.subtract(position); v = rotate(v, orientation.y, -orientation.x); v = v.add(position); if (v.x < 0) { // 最も近い個体が前にいる if (d < SUITABLE) { // 最適距離よりも近い speed /= ACCELERATION; // 速度を落とす } else if (d > SUITABLE) { // 最適距離よりも遠い speed *= ACCELERATION; // 追い上げる } } else if (v.x > 0) { // 最も近い個体が後ろにいる if (d < SUITABLE) { // 最適距離よりも近い speed *= ACCELERATION; // 逃げる } else if (d > SUITABLE) { // 最適距離よりも遠い speed /= ACCELERATION; // 待ち受ける } } speed = Math.min(Math.max(3, speed), 20); // その個体と平行に進もうとする orientation.offset(i.orientation.x * 0.1, i.orientation.y * 0.1); // 重心に向かわせる orientation.offset((c.x - position.x) * 0.01, (c.y - position.y) * 0.01); // (0,0)に向かわせる orientation.offset(-position.x * 0.001, -position.y * 0.001); // orientationの単位ベクトル化 orientation.normalize(1.0); // 移動分Δ var delta:Point = new Point(orientation.x * speed / 20, orientation.y * speed / 20); // 個体の位置を更新 position = position.add(delta); // 範囲に抑え込む position.x = Math.min(Math.max(-100, position.x), 100); position.y = Math.min(Math.max(-100, position.y), 100); // 状態を進める if (--count < 0) { state ^= 1; count = 10/speed; } } /** (0,0)を中心にpをθ回転 * @param p 回転前の点 * @param sint sin(θ) * @param cost cos(θ) * @return 回転後の新しい点 */ public static function rotate(p:Point, sint:Number, cost:Number):Point { return new Point(p.x * cost - p.y * sint, p.x * sint + p.y * cost); } } import flash.display.Graphics; import flash.geom.Point; import flash.geom.Matrix; // 魚の描画データは以下からもらってきた // うお // http://wonderfl.kayac.com/code/1f31037492a64576ea9137cb9046359b6173cde6 class FishPainter { private static const d1:Array = [[new Point(1.4, -2.1), new Point(8, -2.5)], [new Point(10, -4.5), new Point(11, -4.5)], [new Point(11, -2.5), new Point(10, -2.5)], [new Point(14, -2.5), new Point(22, -1.5)], [new Point(26.6, -3), new Point(27, -2)], [new Point(26.6, -1), new Point(26, -1)], [new Point(27.6, -1), new Point(28.8, 1.2)], [new Point(27.6, 2.5), new Point(22, -.5)], [new Point(14, 2.5), new Point(10, 2.5)], [new Point(11, 2.5), new Point(11, 4.5)], [new Point(10, 4.5), new Point(8, 2.5)], [new Point(1.4, 2.1), new Point(0, 0)]]; private static const d2:Array = [[new Point(1.4, -2.1), new Point(8, -2.5)], [new Point(10, -3.5), new Point(11, -3.5)], [new Point(11, -2.5), new Point(10, -2.5)], [new Point(14, -2.5), new Point(22, .5)], [new Point(27.6, -2), new Point(28.8, -1)], [new Point(27.6, 1), new Point(26, 1)], [new Point(26.6, 2), new Point(27, 2.2)], [new Point(26.6, 3.5), new Point(22, 1.5)], [new Point(14, 2.5), new Point(10, 2.5)], [new Point(11, 2.5), new Point(11, 3.5)], [new Point(10, 3.5), new Point(8, 2.5)], [new Point(1.4, 2.1), new Point(0, 0)]]; /** 一個体を描く * @param g 描画先 * @param p 描画位置 * @param o 向き(方向ベクトル) * @param s 状態(0か1) */ public static function paint(graphics:Graphics, p:Point, o:Point, s:int):void { var x:Number = p.x; var y:Number = p.y; var m:Matrix = new Matrix(); m.rotate(Math.atan2(o.y, o.x) + Math.PI); var data:Array = s == 0 ? d1 : d2; graphics.beginFill(0xFF0000); graphics.moveTo(x, y); for each (var d:Array in data) { var c1:Point = m.transformPoint(d[0]); var c2:Point = m.transformPoint(d[1]); graphics.curveTo(x+c1.x, y+c1.y, x+c2.x, y+c2.y); } graphics.endFill(); } } Flock simuration