In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

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;
    }        
}