fish cloud
Boidsのシミュレーション
click: add small fish (小魚)
double click: add a shark(鮫)
/**
* Copyright shohei909 ( http://wonderfl.net/user/shohei909 )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/iGnr
*/
package {
import flash.text.*;
import flash.filters.*;
import flash.geom.*;
import flash.events.*;
import flash.display.*;
import net.hires.debug.Stats;
import com.bit101.components.Label;
[SWF(frameRate = 60)]
final public class Main extends Sprite {
public static const MAX:Number = 1000;
public static const RANGE:Number = 50;//影響半径
public static const V_DIV:uint = Math.ceil(465 / RANGE); //横方向の分割数
public static const H_DIV:uint = Math.ceil(465 / RANGE); //縦方向の分割数
private var map:Vector.<Vector.<Vector.<Particle>>>;
private var particles:Vector.<Particle>;
private var img:BitmapData;
private var numParticles:uint;
private var color:ColorTransform;
private var count:int;
private var press:Boolean;
private var text:Label;
public function Main() {
addEventListener( Event.ADDED_TO_STAGE, initialize );
}
private function initialize( e:Event ):void {
color = new ColorTransform( 0.80, 0.80, 0.85, 1, -3,-2,-2 );
particles = new Vector.<Particle>();
numParticles = 0;
count = 0;
img = new BitmapData(465, 465, false, 0);
addEventListener(Event.EXIT_FRAME, frame);
addChild( new Bitmap(img) );
stage.addEventListener(MouseEvent.MOUSE_DOWN, function(e:MouseEvent):void { press = true; });
stage.addEventListener(MouseEvent.MOUSE_UP, function(e:MouseEvent):void { press = false; } );
stage.addEventListener(MouseEvent.DOUBLE_CLICK, function(e:MouseEvent):void { addShark() } );
stage.quality = "low";
stage.doubleClickEnabled = true;
stage.addChild( new Stats ).alpha = 0.5;
text = new Label( stage, 75, 0, "numParticles: " + numParticles );
map = new Vector.< Vector.< Vector.<Particle> > >(H_DIV);
map.fixed = true;
for(var i:uint = 0; i<H_DIV; i++ ){
map[i] = new Vector.< Vector.<Particle> >(V_DIV);
map[i].fixed = true;
for(var j:uint = 0; j<H_DIV; j++ ){
map[i][j] = new Vector.< Particle >;
}
}
}
private function frame(e:Event):void {
if(press)
addSardine();
img.lock();
img.colorTransform(img.rect, color);
setNeighbors();
move();
img.unlock();
text.text = "numParticles: " + numParticles;
}
private function addSardine():void {
if( numParticles < MAX ){
const p:Particle = particles[numParticles++] = new Sardine( mouseX, mouseY );
p.move();
}
}
private function addShark():void {
const p:Particle = particles[numParticles++] = new Shark( mouseX, mouseY );
p.move();
}
private function setNeighbors():void {
var dx:Number, dy:Number;
const particles:Vector.<Particle> = this.particles;
//分割された各領域ごとで近くのパーティクルをさがす。
for(var i:uint= 0; i < H_DIV; i++) {
const mapi:Vector.<Vector.<Particle>> = map[i];
for(var j:uint = 0; j < V_DIV; j++){
var mapij:Vector.<Particle> = mapi[j];
var minN:int = i-1; if( minN < 0 ){ minN = 0 }
var minM:int = j-1; if( minM < 0 ){ minM = 0 }
var maxN:int = i+2; if( maxN > H_DIV ){ maxN = H_DIV }
var maxM:int = j+2; if( maxM > V_DIV ){ maxM = V_DIV }
for each( var pi:Particle in mapij ){
l:{
for(var n:uint = minN; n < maxN; n++ ){
const mapn:Vector.<Vector.<Particle>> = map[n];
for(var m:uint = minM; m < maxM; m++ ){
const mapnm:Vector.<Particle> = mapn[m];
for each(var pj:Particle in mapnm ){
//piと同じパーティクルが現れたら終了
if ( pi == pj ) { break l; }
pi.lookAt( pj );
pj.lookAt( pi );
}
}
}
}
}
}
}
}
private function move():void {
count++;
const img:BitmapData = this.img;
const particles:Vector.<Particle> = this.particles;
for(var i:uint = 0; i < numParticles; i++) {
const pi:Particle = particles[i];
if ( pi.life <= 0 ) {
particles.splice( i, 1 );
pi.moveMap( map );
numParticles--;
i--;
}else{
pi.move();
pi.moveMap( map );
pi.draw( img );
}
}
}
}
}
import flash.display.*;
import flash.geom.*;
class Particle {
public var x:Number; //x方向の位置
public var y:Number; //y方向の位置
public var mapX:uint;
public var mapY:uint;
public var vx:Number; //vx方向の位置
public var vy:Number; //vy方向の位置
public var fx:Number; //x方向の加速度
public var fy:Number; //y方向の加速度
public static const DF:Number = 0.96; //抵抗係数
public var color:uint = 0xFFFFFF;
public var life:int = 1;
public function Particle(x:Number, y:Number) {
this.x = x;
this.y = y;
vx = vy = 0;
fx = fy = 0;
}
public function move():void {
x += vx += fx;
y += vy += fy;
vx *= DF;
vy *= DF;
fx = 0;
fy = 0;
if (x < 1 ) { vy = 0; vx = 0; x = 1; }
else if(x > 464 ){ vy = 0; vx = 0; x = 464 }
if(y < 1 ){ vy = 0; vx = 0; y = 1; }
else if(y > 464 ){ vy = 0; vx = 0; y = 464 }
}
public function lookAt( p:Particle ):void {}
public function moveMap( map:Vector.<Vector.<Vector.<Particle>>> ):void {
const mx:int = this.x / Main.RANGE;
const my:int = this.y / Main.RANGE;
const pmx:int = this.mapX;
const pmy:int = this.mapY;
if( life == 0 || mx != pmx || my != pmy ){
const mp:Vector.<Particle> = map[ pmx ][ pmy ]
const index:int = mp.indexOf( this );
if ( index != -1) {
mp.splice( index, 1 );
}
if( life != 0 ){
map[ mx ][ my ].push( this );
this.mapY = my; this.mapX = mx;
}
}
}
public function draw( img:BitmapData ):void{
img.setPixel( x, y, color )
}
}
class Fish extends Particle {
public var neighbor:Particle;
public var d2:Number;
public var dx:Number;
public var dy:Number;
function Fish( x:Number, y:Number ){
super( x, y );
}
//壁際の処理
protected function wall( push:Number, r:Number ):void{
if ( x < r ) { fx += push / x; }
else if ( x > 465 - r ) { fx -= push / (465 - x); }
if ( y < r ) { fy += push / y; }
else if ( y > 465 - r ) { fy -= push / (465 - y); }
}
//前方に加速
protected function thrust( f:Number, r:Number ):void {
var vx2:Number = r * ( 1 - 2 * Math.random() ) + vx;
var vy2:Number = r * ( 1 - 2 * Math.random() ) + vy;
var v:Number = f * Math.random() / Math.sqrt( vx2 * vx2 + vy2 * vy2 );
fx += vx2 * v;
fy += vy2 * v;
}
protected function guide( pull:Number, push:Number ):void {
var d:Number = Math.sqrt( d2 );
var f:Number = -push/(d*d) + pull;
fx += dx * f;
fy += dy * f;
}
protected function escape( f:Number ):void {
var r:Number = f / Math.sqrt( dx*dx + dy*dy );
fx -= dx * r;
fy -= dy * r;
}
protected function chase( f:Number ):void {
var r:Number = f// / Math.sqrt( dx*dx + dy*dy );
fx += dx * r;
fy += dy * r;
}
//速度を丸める
protected function round( min:Number, max:Number ):void{
var f2:Number = fx * fx + fy * fy;
var r:Number;
if ( f2 == 0 ) {
}else{
if ( f2 < min*min ) {
r = min / Math.sqrt( f2 );
fx *= r;
fy *= r;
}else if( f2 > max*max ){
r = max / Math.sqrt( f2 );
fx *= r;
fy *= r;
}
}
}
}
class Sardine extends Fish {
private static const MAX:Number = 0.08; //最大の加速度
private static const MIN:Number = 0; //最小の加速度
private static const RANGE:Number = 40; //視界半径
private static const PULL:Number = 0.01; //引き寄せる力
private static const PUSH:Number = 0.5; //離れる力
private static const WALL:Number = 0.8; //壁から離れる力
private static const THRUST:Number = 0.05; //前方へ進む力
private static const RAND:Number = 0.2; //気まぐれの大きさ
private static const RAND2:Number = 0.5; //気まぐれの大きさ2
function Sardine( x:Number, y:Number ){
super( x, y );
vx = ( 1 - 2 * Math.random() ) * .5;
vy = ( 1 - 2 * Math.random() ) * .5;
}
override public function move():void {
var f:Number, d:Number, dx:Number, dy:Number;
if( neighbor is Shark ){
escape( MAX );
}else{
thrust( THRUST, RAND );
if ( neighbor is Fish ) {
guide( PULL, PUSH );
}
}
neighbor = null;
d2 = RANGE * RANGE;
wall( WALL, RANGE );
round( MIN, MAX );
super.move();
}
override public function lookAt( p:Particle ):void {
var dx:Number, dy:Number, d2:Number;
if( p is Shark ){
if ( neighbor is Sardine ) {
this.dx = p.x - x;
this.dy = p.y - y;
this.d2 = dx * dx + dy * dy;
neighbor = p;
return;
}
dx = p.x - x;
dy = p.y - y;
d2 = dx * dx + dy * dy;
if ( d2 < this.d2 ) {
this.d2 = d2;
this.dx = dx;
this.dy = dy;
neighbor = p;
}
}else if( p is Sardine ){
if ( neighbor is Shark ) { return; }
if( Math.random() < RAND2 ){ return; } //時々一番近い魚の以外にも注目する。
dx = p.x - x;
dy = p.y - y;
d2 = dx * dx + dy * dy;
if ( d2 < this.d2 ) {
this.d2 = d2;
this.dx = dx;
this.dy = dy;
neighbor = p;
}
}
}
}
class Shark extends Fish {
private static const MAX:Number = 0.1; //最大の加速度
private static const MIN:Number = 0.06; //最小の加速度
private static const RANGE:Number = 50; //視界半径
private static const PUSH:Number = 0.5; //離れる力
private static const WALL:Number = 1; //壁から離れる力
private static const THRUST:Number = 0.08; //前方へ進む力
private static const RAND:Number = 1; //気まぐれの大きさ
private static const SIZE:Number = 2;
private var shape:Shape = new Shape;
function Shark( x:Number, y:Number ){
super( x, y );
vx = ( 1 - 2 * Math.random() ) * .5;
vy = ( 1 - 2 * Math.random() ) * .5;
with ( shape.graphics ) {
beginFill( 0x6688FF, 1 );
drawCircle( 0, 0, SIZE );
}
}
override public function move():void {
var f:Number, d:Number, dx:Number, dy:Number;
if ( neighbor is Sardine ) {
chase( MAX );
}else {
thrust( THRUST, RAND );
if( neighbor is Shark ){
guide( 0, PUSH )
}
}
neighbor = null;
d2 = RANGE * RANGE;
wall( WALL, RANGE );
round( MIN, MAX );
super.move();
}
override public function lookAt( p:Particle ):void {
var dx:Number, dy:Number, d2:Number;
if( p is Sardine ){
if ( neighbor is Shark ) {
this.dx = p.x - x;
this.dy = p.y - y;
this.d2 = dx * dx + dy * dy;
neighbor = p;
return;
}
dx = p.x - x;
dy = p.y - y;
d2 = dx * dx + dy * dy;
if ( d2 < this.d2 ) {
this.d2 = d2;
this.dx = dx;
this.dy = dy;
neighbor = p;
}
if( d2 < SIZE*SIZE ){ p.life = 0; }
}else if( p is Shark ){
if ( neighbor is Sardine ) { return; }
dx = p.x - x;
dy = p.y - y;
d2 = dx * dx + dy * dy;
if ( d2 < this.d2 ) {
this.d2 = d2;
this.dx = dx;
this.dy = dy;
neighbor = p;
}
}
}
override public function draw(img:BitmapData):void {
img.draw( shape, new Matrix(1,0,0,1,x,y) );
}
}