Starling Boids
StarlingでGPU使ってBoids描画。
数量とかもろもろ設定はconfigボタンを押すとウィンドウが開きます。showのチェックを入れると視界範囲やベクトルも表示されます。大量に出す場合は画面広くしたほうが見やすいです。
boidsの基本3ルール(cohere/align/separate)以外に、異なる色同士は反発するルールを付加しています。(なので、単色にすればスタンダードなboids)
あと画面端はループするか反射か選べます。
/**
* Copyright miyaoka ( http://wonderfl.net/user/miyaoka )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/cn9V
*/
package
{
import com.bit101.components.CheckBox;
import com.bit101.components.HUISlider;
import com.bit101.components.Label;
import com.bit101.components.NumericStepper;
import com.bit101.components.PushButton;
import com.bit101.components.RadioButton;
import com.bit101.components.Window;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageQuality;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.geom.Rectangle;
import starling.core.Starling;
import starling.events.Event;
[SWF(frameRate="60", backgroundColor="#111111")]
public class StarlingBoids extends flash.display.Sprite
{
private var _bm:BoidsModel;
private var _starling:Starling;
private var _confWindow:Window;
private var _boidsCountNS:NumericStepper;
private var _boidsTypeNS:NumericStepper;
private var _cohRangeSlider:HUISlider;
private var _sepRangeSlider:HUISlider;
private var _maxSpeedSlider:HUISlider;
private var _maxForceSlider:HUISlider;
private var _cohSlider:HUISlider;
private var _aliSlider:HUISlider;
private var _sepSlider:HUISlider;
private var _frameRateSlider:HUISlider;
private var _showCohRangeChk:CheckBox;
private var _showSepRangeChk:CheckBox;
private var _showCohForceChk:CheckBox;
private var _showAliForceChk:CheckBox;
private var _showSepForceChk:CheckBox;
private static const LABEL_BOUND_LOOP:String = "loop";
private static const LABEL_BOUND_HARD:String = "bound";//"hard bound";
private static const LABEL_BOUND_SOFT:String = "soft bound";
private var gridUnitSize:uint = 50;
public function StarlingBoids()
{
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
stage.quality = StageQuality.LOW;
initUI();
initModel();
initStarling();
}
private function initUI():void
{
//呼び出しボタン
var confBtn:PushButton = new PushButton(this, 50, 4, "config", showConfig);
addChild(confBtn);
//設定ウィンドウ
_confWindow = new Window(null, 50, 4, "config");
var ux:Number;
var uy:Number;
var uBaseY:Number = 24;
//各種UI
var countLabel:Label = new Label(_confWindow, ux=4, uy=uBaseY, "boids");
var typeLabel:Label = new Label(_confWindow, ux, uy+=20, "colors");
_boidsCountNS = new NumericStepper(_confWindow, ux+=32, uy=uBaseY+2, onChangeBoidsCount);
_boidsTypeNS = new NumericStepper(_confWindow, ux, uy+=20, onChangeBoidsCount);
var edgeLabel:Label = new Label(_confWindow, ux=8, uy+=28, "[when out of stage]");
new RadioButton(_confWindow, ux+=4, uy+=20, LABEL_BOUND_LOOP, true, onSelectBounding);
new RadioButton(_confWindow, ux, uy+=16, LABEL_BOUND_HARD, false, onSelectBounding);
// new RadioButton(_confWindow, ux, uy+=16, LABEL_BOUND_SOFT, false, onSelectBounding);
var frameRateLabel:Label = new Label(_confWindow, ux=128, uy=uBaseY, "frameRate");
var cohRangeLabel:Label = new Label(_confWindow, ux, uy+=24, "cohRange");
var sepRangeLabel:Label = new Label(_confWindow, ux, uy+=16, "sepRange");
var maxSpeed:Label = new Label(_confWindow, ux, uy+=16, "maxSpeed");
var maxForce:Label = new Label(_confWindow, ux, uy+=16, "maxForce");
var forces:Label = new Label(_confWindow, ux, uy+=24, "[forces]");
var cohLabel:Label = new Label(_confWindow, ux, uy+=16, "Cohision");
var aliLabel:Label = new Label(_confWindow, ux, uy+=16, "Alignment");
var sepLabel:Label = new Label(_confWindow, ux, uy+=16, "Separation");
_frameRateSlider = new HUISlider(_confWindow, ux+=40, uy=uBaseY, "", onChangeFR);
_cohRangeSlider = new HUISlider(_confWindow, ux, uy+=24, "", onChangeCohRange);
_sepRangeSlider = new HUISlider(_confWindow, ux, uy+=16, "", onChangeSepRange);
_maxSpeedSlider = new HUISlider(_confWindow, ux, uy+=16, "", onChangeMaxSpeed);
_maxForceSlider = new HUISlider(_confWindow, ux, uy+=16, "", onChangeMaxForce);
_cohSlider = new HUISlider(_confWindow, ux, uy+=24+16, "", onChangeAmps);
_aliSlider = new HUISlider(_confWindow, ux, uy+=16, "", onChangeAmps);
_sepSlider = new HUISlider(_confWindow, ux, uy+=16, "", onChangeAmps);
var show:Label = new Label(_confWindow, ux+=172, uy=uBaseY+12, "[show]");
_showCohRangeChk = new CheckBox(_confWindow, ux+=10, uy=_cohRangeSlider.y+4, "", onChangeShowCohRange);
_showSepRangeChk = new CheckBox(_confWindow, ux, uy+=16, "", onChangeShowSepRange);
_showCohForceChk = new CheckBox(_confWindow, ux, uy=_cohSlider.y+4, "", onChangeShowCohForce);
_showAliForceChk = new CheckBox(_confWindow, ux, uy+=16, "", onChangeShowAliForce);
_showSepForceChk = new CheckBox(_confWindow, ux, uy+=16, "", onChangeShowSepForce);
_confWindow.setSize(376,194);
var closeBtn:PushButton = new PushButton(_confWindow, _confWindow.width - 18, 2, "x", hideConfig);
closeBtn.setSize(16,16);
//初期値
_boidsCountNS.value = 200;
_boidsCountNS.step = 50;
_boidsCountNS.minimum = 0;
_boidsTypeNS.value = 3;
_boidsTypeNS.step = 1;
_boidsTypeNS.minimum = 1;
_boidsTypeNS.maximum = 8;
_cohRangeSlider.maximum = 200;
_sepRangeSlider.maximum = 1.0;
_maxSpeedSlider.maximum = 30;
_maxForceSlider.maximum = 1.0;
_cohRangeSlider.value = 45;
_sepRangeSlider.value = 0.3;
_maxSpeedSlider.value = 2;
_maxForceSlider.value = 0.5;
_cohSlider.value = 30;
_aliSlider.value = 30;
_sepSlider.value = 50;
_showCohRangeChk.selected = false;
_showSepRangeChk.selected = false;
_frameRateSlider.maximum = 60;
_frameRateSlider.minimum = 0;
_frameRateSlider.value = stage.frameRate;
}
private function showConfig(e:flash.events.Event):void
{
addChild(_confWindow)
}
private function hideConfig(e:flash.events.Event):void
{
removeChild(_confWindow)
}
private function initModel():void
{
_bm = new BoidsModel(new Rectangle(0, 0, stage.stageWidth, stage.stageHeight), gridUnitSize);
onChangeCohRange(null);
onChangeSepRange(null);
onChangeMaxSpeed(null);
onChangeMaxForce(null);
onChangeBoidsCount(null);
onChangeAmps(null);
_bm.bounding = BoidsModel.BOUND_LOOP;
}
private function initStarling():void
{
_starling = new Starling(StarlingBoidsView, stage);
_starling.showStats = true;
_starling.enableErrorChecking = false;
_starling.addEventListener(starling.events.Event.ROOT_CREATED, onRootCreated);
_starling.start();
}
private function onRootCreated(e:starling.events.Event):void
{
StarlingBoidsView.instance.init(_bm);
onChangeShowCohRange(null);
onChangeShowSepRange(null);
}
private function onSelectBounding(e:flash.events.Event):void
{
switch(e.target.label)
{
case LABEL_BOUND_LOOP:
_bm.bounding = BoidsModel.BOUND_LOOP;
break;
case LABEL_BOUND_HARD:
_bm.bounding = BoidsModel.BOUND_HARD;
break;
case LABEL_BOUND_SOFT:
_bm.bounding = BoidsModel.BOUND_SOFT;
break;
}
}
private function onChangeShowCohRange(e:flash.events.Event):void
{
StarlingBoidsView.instance.showCohRange = _showCohRangeChk.selected;
}
private function onChangeShowSepRange(e:flash.events.Event):void
{
StarlingBoidsView.instance.showSepRange = _showSepRangeChk.selected;
}
private function onChangeShowCohForce(e:flash.events.Event):void
{
StarlingBoidsView.instance.showCohForce = _showCohForceChk.selected;
}
private function onChangeShowAliForce(e:flash.events.Event):void
{
StarlingBoidsView.instance.showAliForce = _showAliForceChk.selected;
}
private function onChangeShowSepForce(e:flash.events.Event):void
{
StarlingBoidsView.instance.showSepForce = _showSepForceChk.selected;
}
private function onChangeBoidsCount(e:flash.events.Event):void
{
_bm.reset(_boidsCountNS.value, _boidsTypeNS.value);
}
private function onChangeAmps(e:flash.events.Event):void
{
_bm.changeAmps(_cohSlider.value, _aliSlider.value, _sepSlider.value);
}
private function onChangeFR(e:flash.events.Event):void
{
stage.frameRate = _frameRateSlider.value;
}
private function onChangeCohRange(e:flash.events.Event):void
{
_bm.baseCohRange = _cohRangeSlider.value;
}
private function onChangeSepRange(e:flash.events.Event):void
{
_bm.baseSepRange = _sepRangeSlider.value;
}
private function onChangeMaxSpeed(e:flash.events.Event):void
{
_bm.baseSpeed = _maxSpeedSlider.value;
}
private function onChangeMaxForce(e:flash.events.Event):void
{
_bm.baseForce = _maxForceSlider.value;
}
}
}
import flash.display.BitmapData;
import flash.display.Graphics;
import flash.display.Shape;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import starling.core.Starling;
import starling.display.BlendMode;
import starling.display.DisplayObjectContainer;
import starling.display.Image;
import starling.display.QuadBatch;
import starling.display.Sprite;
import starling.events.Event;
import starling.events.ResizeEvent;
import starling.textures.Texture;
internal class StarlingBoidsView extends Sprite
{
private static var _instance:StarlingBoidsView;
private var _boidImage:Image;
private var _circleImage:Image;
private var _forceImage:Image;
private var _boidQBL:QBList;
private var _circleQBL:QBList;
private var _forceQBL:QBList;
private var _bm:BoidsModel;
public var showCohRange:Boolean;
public var showSepRange:Boolean;
public var showCohForce:Boolean;
public var showAliForce:Boolean;
public var showSepForce:Boolean;
private static var colorList:Vector.<uint> = new <uint>[
0xeeeeee,
0xff3333,
0x3333ff,
0x33ff33,
0xffff33,
0xff33ff,
0x33ffff,
0xff9911,
0x1199ff,
0x111111,
0x000000
];
public function StarlingBoidsView()
{
_instance = this;
addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
}
public static function get instance():StarlingBoidsView
{
return _instance;
}
public function init(bm:BoidsModel):void
{
_bm = bm;
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onAddedToStage(event:Event):void
{
touchable = false;
createImages();
_boidQBL = new QBList(this);
_circleQBL = new QBList(this);
_forceQBL = new QBList(this);
stage.addEventListener(ResizeEvent.RESIZE, onResize);
}
private function createImages():void
{
var shape:Shape;
var bmd:BitmapData;
//boid
shape = new Shape();
var g:Graphics = shape.graphics;
g.beginFill(0x999999, 0.8);
g.lineStyle(1, 0xffffff, 1);
g.lineTo(1, 1);
g.lineTo(13, 5);
g.lineTo(1, 9);
g.lineTo(1, 1);
bmd = new BitmapData(14, 10, true, 0x00000000);
bmd.draw(shape);
_boidImage = new Image(Texture.fromBitmapData(bmd, false));
_boidImage.pivotX = _boidImage.width >> 1;
_boidImage.pivotY = _boidImage.height >> 1;
_boidImage.alpha = 0.8;
_boidImage.scaleX = _boidImage.scaleY = .7;
_boidImage.blendMode = BlendMode.ADD;
//circle
g.clear();
g.lineStyle(4, 0xffffff, 1);
g.beginFill(0xffffff, 0.2);
g.drawCircle(200,200,198);
bmd = new BitmapData(400, 400, true, 0x00000000);
bmd.draw(shape);
_circleImage = new Image(Texture.fromBitmapData(bmd, false));
_circleImage.pivotX = _circleImage.width >> 1;
_circleImage.pivotY = _circleImage.height >> 1;
_circleImage.scaleX = _circleImage.scaleY = 1;
_circleImage.blendMode = BlendMode.ADD;
//force arrow
g.clear();
g.lineStyle(2, 0xffffff, 1);
g.moveTo(20,5);
g.lineTo(10, 0);
g.moveTo(20,5);
g.lineTo(10,10);
g.moveTo(20,5);
g.lineTo(0, 5);
bmd = new BitmapData(20, 10, true, 0x00000000);
bmd.draw(shape);
_forceImage = new Image(Texture.fromBitmapData(bmd, false));
_forceImage.pivotX = _forceImage.width >> 1;
_forceImage.pivotY = _forceImage.height >> 1;
_forceImage.scaleX = _forceImage.scaleY = .75;
_forceImage.blendMode = BlendMode.ADD;
_forceImage.alpha = 0.125;
}
private function onResize(e:ResizeEvent):void
{
var viewPortRectangle:Rectangle = new Rectangle();
viewPortRectangle.width = e.width; viewPortRectangle.height = e.height
Starling.current.viewPort = viewPortRectangle;
stage.stageWidth = e.width;
stage.stageHeight = e.height;
_bm.stageRect = Starling.current.viewPort;
}
private function onEnterFrame(event:Event):void
{
_bm.update();
drawImages();
}
private function drawImages():void
{
var b:BoidVO;
var i:int;
var len:uint;
var color:uint;
var boidList:Vector.<BoidVO> = _bm.boidList;
len = boidList.length;
_boidQBL.reset();
_circleQBL.reset();
_forceQBL.reset();
var showAmp:Number = 30;
for (i = 0; i < len; i++)
{
b = boidList[i];
color = colorList[b.type];
_boidImage.x = b.x;
_boidImage.y = b.y;
_boidImage.color = color;
_boidImage.rotation = b.rot;
_boidQBL.addImage(_boidImage);
if(showCohRange)
{
_circleImage.x = b.x;
_circleImage.y = b.y;
_circleImage.alpha = 0.2;
_circleImage.color = color;
_circleImage.scaleX = _circleImage.scaleY = b.cohesionDist / 200;
_circleQBL.addImage(_circleImage);
}
if(showSepRange)
{
_circleImage.x = b.x;
_circleImage.y = b.y;
_circleImage.alpha = 0.5;
_circleImage.color = color;
_circleImage.scaleX = _circleImage.scaleY = b.separationDist / 200;
_circleQBL.addImage(_circleImage);
}
if(showSepForce && (b.sepX != 0 || b.sepY != 0))
{
_forceImage.x = b.x + b.sepX * showAmp;
_forceImage.y = b.y + b.sepY * showAmp;
_forceImage.color = color;
_forceImage.rotation = Math.atan2(b.sepY, b.sepX);
_forceQBL.addImage(_forceImage);
}
if(showCohForce && (b.cohX != 0 || b.cohY != 0))
{
_forceImage.x = b.x + b.cohX * showAmp;
_forceImage.y = b.y + b.cohY * showAmp;
_forceImage.color = color;
_forceImage.rotation = Math.atan2(b.cohY, b.cohX);
_forceQBL.addImage(_forceImage);
}
if(showAliForce && (b.aliX != 0 || b.aliY != 0))
{
_forceImage.x = b.x + b.aliX * showAmp;
_forceImage.y = b.y + b.aliY * showAmp;
_forceImage.color = color;
_forceImage.rotation = Math.atan2(b.aliY, b.aliX);
_forceQBL.addImage(_forceImage);
}
}
}
}
internal class BoidsModel
{
private var _boidList:Vector.<BoidVO>;
private var _bm:BoidModel;
private var _stageRect:Rectangle;
private var _grid:GridModel;
private var _cohAmp:Number = 1;
private var _aliAmp:Number = 1;
private var _sepAmp:Number = 1;
public var avoidOtherTypes:Boolean;
public var bounding:String;
public static const BOUND_LOOP:String = "boundLoop";
public static const BOUND_HARD:String = "boundHard";
public static const BOUND_SOFT:String = "boundSoft";
private var _baseCohRange:Number;
private var _baseSepRange:Number;
private var _baseSpeed:Number;
private var _baseForce:Number;
private var _force:Point = new Point();
public function BoidsModel(stageRect:Rectangle, gridUnitSize:Number = 15)
{
_stageRect = stageRect;
_boidList = new Vector.<BoidVO>();
_grid = new GridModel(stageRect, gridUnitSize);
_bm = new BoidModel();
}
public function get baseForce():Number
{
return _baseForce;
}
public function set baseForce(value:Number):void
{
_baseForce = value;
var i:uint;
var len:uint = boidList.length;
var b:BoidVO;
var f:Number = _baseSpeed * value;
for(i = 0; i < len; i++)
{
b = boidList[i];
b.maxForce = f * rand();
}
}
public function get baseSpeed():Number
{
return _baseSpeed;
}
public function set baseSpeed(value:Number):void
{
_baseSpeed = value;
var i:uint;
var len:uint = boidList.length;
var b:BoidVO;
var f:Number = _baseForce * value;
for(i = 0; i < len; i++)
{
b = boidList[i];
b.maxSpeed = value * rand();
b.maxForce = f;
}
}
public function get baseSepRange():Number
{
return _baseSepRange;
}
public function set baseSepRange(value:Number):void
{
_baseSepRange = value;
var i:uint;
var len:uint = boidList.length;
var b:BoidVO;
var sep:Number = _baseCohRange * value;
for(i = 0; i < len; i++)
{
b = boidList[i];
b.separationDist = sep * rand();
}
}
public function get baseCohRange():Number
{
return _baseCohRange;
}
public function set baseCohRange(value:Number):void
{
_baseCohRange = value;
var i:uint;
var len:uint = boidList.length;
var b:BoidVO;
var sep:Number = _baseSepRange * value;
for(i = 0; i < len; i++)
{
b = boidList[i];
b.cohesionDist = value * rand();
b.separationDist = sep;
}
}
public function changeAmps(coh:Number, ali:Number, sep:Number):void
{
_cohAmp =
_bm.cohAmp = coh;
_aliAmp =
_bm.aliAmp = ali;
_sepAmp =
_bm.sepAmp = sep;
}
public function get boidList():Vector.<BoidVO>
{
return _boidList;
}
public function set boidList(value:Vector.<BoidVO>):void
{
_boidList = value;
}
public function get stageRect():Rectangle
{
return _stageRect;
}
public function set stageRect(value:Rectangle):void
{
_stageRect = _grid.stageRect = value;
}
private function rand(min:Number = 0.5, max:Number = 1.5):Number
{
return Math.random() * (max - min) + min;
}
public function reset(num:uint = 0, typeSize:uint = 1):void
{
_boidList.length = 0;
if(1 > num) return;
var vo:BoidVO;
var i:int;
var type:uint;
var pi2:Number = Math.PI * 2;
var range:Number = pi2 / typeSize;
var spornAngle:Number;
var stageR:Number = Math.sqrt(_stageRect.width * _stageRect.width + _stageRect.height * _stageRect.height) / 2;
var stageCenterH:Number = _stageRect.width/2 + _stageRect.left;
var stageCenterV:Number = _stageRect.height/2 + _stageRect.top;
var spornR:Number;
for(i = 0; i< num; i++)
{
type = Math.floor(Math.random() * typeSize);
// spornAngle = (Math.random() * 0.75 + type) * range;
spornAngle = Math.random() * pi2;
spornR = stageR * (0.1 + Math.random() * 0.7);
vo = new BoidVO(
Math.cos(spornAngle) * spornR + stageCenterH,
Math.sin(spornAngle) * spornR + stageCenterV,
type);
vo.maxSpeed = _baseSpeed * rand();
vo.maxForce = _baseForce * rand() * _baseSpeed;
vo.cohesionDist = _baseCohRange* rand();
vo.separationDist = _baseSepRange * rand() * _baseCohRange;
vo.vx = _baseSpeed * (Math.random() - 0.5)
vo.vy = _baseSpeed * (Math.random() - 0.5)
_boidList.push(vo);
}
}
public function update():void
{
updateGrid();
calcBoidRules();
}
private function updateGrid():void
{
//Grid内容を初期化
_grid.reset();
//Boidを該当グリッドにセットする
var i:uint;
var len:uint = boidList.length;
for (i = 0; i < len; i++)
{
_grid.addValue(boidList[i]);
}
}
private function calcBoidRules():void
{
var i:uint;
var len:uint = boidList.length;
var gridUnitSize:Number = _grid.gridUnitSize;
var b:BoidVO;
var distSq:Number;
var dist:Number;
for(i = 0; i < len; i++)
{
b = boidList[i];
_bm.boid = b;
_bm.update(_grid, _stageRect, gridUnitSize);
switch(bounding)
{
case BOUND_LOOP:
_bm.looping(_stageRect);
break;
case BOUND_HARD:
_bm.bounding(_stageRect);
break;
}
_force.x = b.vx + b.ax * b.maxForce;
_force.y = b.vy + b.ay * b.maxForce;
Util.limitNorm(_force, b.maxSpeed);
b.vx = _force.x * b.maxSpeed;
b.vy = _force.y * b.maxSpeed;
b.x += b.vx;
b.y += b.vy;
b.rot = Math.atan2(b.vy, b.vx);
}
}
}
internal class BoidModel
{
public var boid:BoidVO;
private var _force:Point = new Point;
private var _cohFXmean:Number;
private var _cohFYmean:Number;
private var _cohEXmean:Number;
private var _cohEYmean:Number;
private var _aliXmean:Number;
private var _aliYmean:Number;
private var _sepXmean:Number;
private var _sepYmean:Number;
private var _sepDistSq:Number;
private var _cohDistSq:Number;
private var _cohFCount:uint;
private var _cohECount:uint;
private var _aliCount:uint;
private var _sepCount:uint;
private var _cohAmp:Number = 1;
private var _aliAmp:Number = 1;
private var _sepAmp:Number = 1;
private var _ampSum:Number = _cohAmp + _aliAmp + _sepAmp;
public function get sepAmp():Number
{
return _sepAmp;
}
public function set sepAmp(value:Number):void
{
_sepAmp = value;
updateAmpSum();
}
public function get aliAmp():Number
{
return _aliAmp;
}
public function set aliAmp(value:Number):void
{
_aliAmp = value;
updateAmpSum();
}
public function get cohAmp():Number
{
return _cohAmp;
}
public function set cohAmp(value:Number):void
{
_cohAmp = value;
updateAmpSum();
}
private function updateAmpSum():void
{
_ampSum = _cohAmp + _aliAmp + _sepAmp;
}
/**
* boidsルールを計算する
*/
public function update(grid:GridModel, stageRect:Rectangle, gridUnitSize:uint):void
{
seek(grid);
var ax:Number = 0;
var ay:Number = 0;
_force.x = _force.y = 0;
var cohCount:uint = _cohFCount + _cohECount;
//[cohere]
if(1 < _cohFCount)
{
//自身以外にある場合
_cohFXmean = _cohFXmean / _cohFCount - boid.x;
_cohFYmean = _cohFYmean / _cohFCount - boid.y;
}
else
{
_cohFXmean = _cohFYmean = 0;
}
if(0 < _cohECount)
{
_cohEXmean = _cohEXmean / _cohECount - boid.x;
_cohEYmean = _cohEYmean / _cohECount - boid.y;
}
//足して範囲で正規化してアンプを掛ける
if(0 < cohCount)
{
_force.x = _cohFXmean * _cohFCount / cohCount - _cohEXmean * _cohECount / cohCount;
_force.y = _cohFYmean * _cohFCount / cohCount - _cohEYmean * _cohECount / cohCount;
}
Util.limitNorm(_force, boid.cohesionDist);
boid.cohX = _force.x;
boid.cohY = _force.y;
ax += _force.x * _cohAmp;
ay += _force.y * _cohAmp;
//[align]
if(1 < _aliCount)
{
//自身以外にある場合
_aliXmean = _aliXmean / _aliCount;
_aliYmean = _aliYmean / _aliCount;
}
else
{
_aliXmean = _aliYmean = 0;
}
_force.x = _aliXmean;
_force.y = _aliYmean;
Util.limitNorm(_force, boid.maxSpeed);
boid.aliX = _force.x;
boid.aliY = _force.y;
ax += _force.x * _aliAmp;
ay += _force.y * _aliAmp;
//[separate]
if(0 < _sepCount)
{
_sepXmean = _sepXmean / _sepCount - boid.x;
_sepYmean = _sepYmean / _sepCount - boid.y;
_force.x = _sepXmean;
_force.y = _sepYmean;
sepNorm(_force, boid.separationDist);
}
boid.sepX = -_force.x;
boid.sepY = -_force.y;
ax -= _force.x * _sepAmp;
ay -= _force.y * _sepAmp;
//トータル
_force.x = ax;
_force.y = ay;
if(0 < _ampSum) Util.limitNorm(_force, _ampSum);
boid.ax = _force.x;
boid.ay = _force.y;
}
private function seek(grid:GridModel):void
{
_cohFXmean = _cohFYmean =
_cohEXmean = _cohEYmean =
_aliXmean = _aliYmean =
_sepXmean = _sepYmean =
_cohFCount = _cohECount = _aliCount = _sepCount = 0;
_sepDistSq = boid.separationDist * boid.separationDist * 2;
_cohDistSq = boid.cohesionDist * boid.cohesionDist * 2;
grid.map(seekCallback, boid.x, boid.y, boid.cohesionDist);
}
private function seekCallback(gridValue:Vector.<BoidVO>):void
{
var len:uint = gridValue.length;
if(0 == len) return;
var i:uint;
var t:BoidVO;
var distSq:Number;
for(i = 0; i < len; i++)
{
t = gridValue[i];
distSq = Util.getDistSquare(t.x - boid.x, t.y - boid.y);
if(distSq > _cohDistSq) continue;
//coh
if(t.type == boid.type)
{
//同タイプを集計
_cohFXmean += t.x;
_cohFYmean += t.y;
_cohFCount++;
//alignは同タイプのみ
_aliXmean += t.vx;
_aliYmean += t.vy;
_aliCount++;
}
else
{
//異タイプを集計
_cohEXmean += t.x;
_cohEYmean += t.y;
_cohECount++;
}
if(distSq > _sepDistSq) continue;
if(t == boid) continue;
_sepXmean += t.x;
_sepYmean += t.y;
_sepCount++;
}
}
/**
* [optionルール]
* ステージ境界を超えたら逆向きに押し戻す力を加える
*/
public function bounding(stageRect:Rectangle):void
{
if(boid.x > stageRect.right)
{
boid.x = stageRect.right;
boid.vx *= -1;
}
else if(boid.x < stageRect.left)
{
boid.x = stageRect.left;
boid.vx *= -1;
}
if(boid.y > stageRect.bottom)
{
boid.y = stageRect.bottom;
boid.vy *= -1;
}
else if(boid.y < stageRect.top)
{
boid.y = stageRect.top;
boid.vy *= -1;
}
}
public function looping(stageRect:Rectangle):void
{
if(boid.x > stageRect.right)
{
boid.x = stageRect.left;
}
else if(boid.x < stageRect.left)
{
boid.x = stageRect.right;
}
if(boid.y > stageRect.bottom)
{
boid.y = stageRect.top;
}
else if(boid.y < stageRect.top)
{
boid.y = stageRect.bottom;
}
}
private function sepNorm(pt:Point, limit:Number):void
{
var x:Number = pt.x / limit;
var y:Number = pt.y / limit;
var distSq:Number = Util.getDistSquare(x, y);
if(distSq > 1)
{
//範囲外ならsepを加えない
x = 0;
y = 0;
}
else
{
//距離が近いほど大きくする
var dist:Number = Math.sqrt(distSq);
x = x / dist * (1-dist);
y = y / dist * (1-dist);
}
pt.x = x;
pt.y = y;
}
}
internal class GridModel
{
private var _stageRect:Rectangle;
private var _gridUnitSize:Number;
private var _gridList:Vector.<Vector.<Vector.<BoidVO>>>;
private var _gridCountX:uint;
private var _gridCountY:uint;
private var _gridLeft:Number;
private var _gridTop:Number;
private var _gridRight:Number;
private var _gridBottom:Number;
public function get gridUnitSize():Number
{
return _gridUnitSize;
}
public function set gridUnitSize(value:Number):void
{
_gridUnitSize = value;
init();
}
public function get stageRect():Rectangle
{
return _stageRect;
}
public function set stageRect(value:Rectangle):void
{
_stageRect = value;
init();
}
public function GridModel(stageRect:Rectangle, gridUnitSize:Number)
{
_gridUnitSize = gridUnitSize;
_stageRect = stageRect;
init();
}
/**
* グリッド配列を決定し、全グリッド内に初期オブジェクトを配置
*/
public function init():void
{
//stageRectから縦横グリッド数を算出
_gridCountX = (_stageRect.width / _gridUnitSize >> 0) + 1;
_gridCountY = (_stageRect.height / _gridUnitSize >> 0) + 1;
//グリッドのrectを設定
_gridLeft = _stageRect.left;
_gridTop = _stageRect.top;
_gridRight = _gridUnitSize * _gridCountX + _gridLeft;
_gridBottom = _gridUnitSize * _gridCountY + _gridTop;
//グリッド内容を初期化
_gridList = new Vector.<Vector.<Vector.<BoidVO>>>(_gridCountX, true);
var i:uint;
var j:uint;
for (i = 0; i < _gridCountX; i++)
{
_gridList[i] = new Vector.<Vector.<BoidVO>>(_gridCountY, true);
for(j = 0; j < _gridCountY; j++)
{
_gridList[i][j] = new Vector.<BoidVO>;
}
}
}
/**
* 全グリッドの内容物の値をリセット
*/
public function reset():void
{
var i:uint;
var j:uint;
for (i = 0; i < _gridCountX; i++)
{
for (j = 0; j < _gridCountY; j++)
{
_gridList[i][j].length = 0;
}
}
}
/**
* boidの座標から該当するグリッドに追加
*/
public function addValue(boid:BoidVO):void
{
var gridValue:Vector.<BoidVO> = getGridValue(boid.x, boid.y);
if(gridValue) gridValue.push(boid);
}
/**
* 座標からグリッドの値を取得
* インデックス範囲外ならnullを返す
*/
public function getGridValue(x:Number, y:Number):Vector.<BoidVO>
{
return (_gridLeft > x) ? null :
(_gridTop > y) ? null :
(x >= _gridRight) ? null :
(y >= _gridBottom) ? null :
_gridList[(x - _gridLeft) / _gridUnitSize >> 0][(y - _gridTop) / _gridUnitSize >> 0];
}
public function map(callback:Function, x:Number, y:Number, dist:Number):void
{
var rx:int;
var ry:int;
var gridRange:uint = (dist / gridUnitSize >> 0) + 1;
var distSq:Number = dist * dist / (gridUnitSize * gridUnitSize);
var offsetDistSq:Number;
var isHit:Boolean;
for(rx = gridRange; rx > 0; rx--)
{
for(ry = rx; ry > 0; ry--)
{
offsetDistSq = Util.getDistSquare(rx-1, ry-1);
if(offsetDistSq > distSq) continue;
if(!isHit)
{
isHit = true;
callbackCenterGrid(x, y, rx, callback);
}
callbackSymGrid(x, y, rx, ry, callback);
for(ry = ry - 1; ry > 0; ry--)
{
callbackSymGrid(x, y, rx, ry, callback);
}
}
}
}
private function callbackSymGrid(x:Number, y:Number, rx:int, ry:int, callback:Function):void
{
callbackQuadGrid(x,y,rx,ry, callback);
if(rx == ry) return;
callbackQuadGrid(x,y,ry,rx, callback);
}
private function callbackQuadGrid(x:Number, y:Number, offsetGx:Number, offsetGy:Number, callback:Function):void
{
var offsetX:Number;
var offsetY:Number;
offsetX = offsetGx * gridUnitSize;
offsetY = offsetGy * gridUnitSize;
callbackGrid(x + offsetX, y + offsetY, callback);
callbackGrid(x - offsetX, y + offsetY, callback);
callbackGrid(x + offsetX, y - offsetY, callback);
callbackGrid(x - offsetX, y - offsetY, callback);
}
private function callbackCenterGrid(x:Number, y:Number, rx:int, callback:Function):void
{
var offset:Number;
var offsetSq:Number;
var i:uint;
for(i = rx; i > 0; i--)
{
offset = i * gridUnitSize;
callbackGrid(x + offset, y, callback);
callbackGrid(x - offset, y, callback);
callbackGrid(x, y + offset, callback);
callbackGrid(x, y - offset, callback);
}
callbackGrid(x, y, callback);
}
private function callbackGrid(x:Number, y:Number, callback:Function):void
{
var gridValue:Vector.<BoidVO> = getGridValue(x, y);
if(!gridValue) return;
callback(gridValue)
}
}
internal class BoidVO
{
//Position
public var x:Number;
public var y:Number;
public var rot:Number;
//Velocity
public var vx:Number = 0;
public var vy:Number = 0;
public var maxSpeed:Number = 2.5;
//Acceleration
public var ax:Number = 0;
public var ay:Number = 0;
public var maxForce:Number = 0.5;
//range (radius)
public var cohesionDist:Number;
public var separationDist:Number;
public var type:uint = 0;
//option
public var cohX:Number;
public var cohY:Number;
public var aliX:Number;
public var aliY:Number;
public var sepX:Number;
public var sepY:Number;
public function BoidVO(x:Number, y:Number, type:uint)
{
this.x = x;
this.y = y;
this.type = type;
}
}
internal class QBList
{
private static const MAX_QUAD_BATCH_COUNT:int = 8192;
private var _imgIndex:int;
private var _qbList:Vector.<QuadBatch>;
private var _currentQB:QuadBatch;
private var _display:DisplayObjectContainer;
private var _qbListIndex:int;
private var _qbListLen:uint;
public function QBList(display:DisplayObjectContainer)
{
_display = display;
_qbList = new Vector.<QuadBatch>();
_qbListLen = 0;
_imgIndex = -1;
}
/**
* バッチ内容追加
* バッチ内容数が最大(8192)を超えたら新しいバッチを生成してバッチリストに追加
*/
public function addImage(image:Image, parentAlpha:Number=1.0, modelViewMatrix:Matrix=null, blendMode:String=null):void
{
//バッチ内容数がフローしたら
if(_qbListIndex != ++_imgIndex / MAX_QUAD_BATCH_COUNT >> 0)
{
_qbListIndex++;
//そのインデックスのバッチが存在しなければ生成
if(_qbListLen == _qbListIndex)
{
_qbList[_qbListIndex] = new QuadBatch();
_qbListLen++;
_display.addChild(_qbList[_qbListIndex]);
}
//インデックスに応じたバッチをセット
_currentQB = _qbList[_qbListIndex]
}
_currentQB.addImage(image, parentAlpha, modelViewMatrix, blendMode);
}
/**
* 全てのバッチリストからバッチ内容を消去
* (確保したバッチ自体は残る)
*/
public function reset():void
{
var i:int;
for (i = 0; i < _qbList.length; i++)
{
_qbList[i].reset();
}
_imgIndex = -1;
_qbListIndex = -1;
}
/**
* バッチリストをGC
*/
public function dispose():void
{
var i:int;
for (i = 0; i < _qbList.length; i++)
{
_qbList[i].dispose();
_display.removeChild(_qbList[i]);
}
_qbList = new Vector.<QuadBatch>();
_imgIndex = -1;
_qbListIndex = -1;
_qbListLen = 0;
_currentQB = null;
}
}
internal class Util
{
/**
* ベクトルを0-1の範囲に正規化
* 制限範囲以上なら1にノーマライズする
*/
public static function limitNorm(pt:Point, limit:Number):void
{
var x:Number = pt.x / limit;
var y:Number = pt.y / limit;
var distSq:Number = getDistSquare(x, y);
if(distSq > 1)
{
var dist:Number = Math.sqrt(distSq);
x /= dist;
y /= dist;
}
pt.x = x;
pt.y = y;
}
/**
* 距離の平方を返す
* (距離の大小の比較用。sqrt演算を省いて処理高速化)
*/
public static function getDistSquare(x:Number, y:Number):Number
{
return x * x + y * y;
}
}