Boids test 2
// forked from j000's Boids test (forked from: forked from: Boid)
// forked from nyonyonyo's forked from: Boid
// forked from wanson's Boid
package {
import flash.display.Graphics;
import flash.display.Sprite;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.filters.GlowFilter;
import flash.filters.DropShadowFilter;
import flash.utils.Timer;
import flash.utils.getTimer;
import flash.events.TimerEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.geom.Point;
import net.hires.debug.Stats;
[SWF(width="600", height="600", frameRate="40", backgroundColor="0xddddff")]
public class BoidDemo extends Sprite {
private const NUMBOIDS:Number = 25;
private const SIZE:int = stage.stageHeight/50;
private const tf:TextFormat = new TextFormat("Lucida Console", SIZE);
private var boids:Array = new Array();
private var sprites:Array = new Array();
private var speeds:Array = new Array();
private var fail:Array = new Array();
private var steps:int = 0, fails:int = 0, stepsfield:TextField, failsfield:TextField, timefield:TextField;
private var gotarget:Boolean = false, target:Point = new Point(-1, -1);
private var filter:DropShadowFilter = new DropShadowFilter(8, 45, 0, 0.8), filterset:Array = [filter];
private var move:Boolean = false, moves:int = 0;
private var drawer:Sprite;
private var bmpdata:BitmapData;
private var timer:int, timer2:int, fps:int;
private var b:Boid, i:int;
public function BoidDemo() {
Wonderfl.capture_delay(40);
inittrace(stage);
stage.quality = "LOW";
bmpdata = new BitmapData(stage.stageWidth, stage.stageHeight, true, 0x00ffffff);
this.addChild(new Bitmap(bmpdata));
var tmpw:int = stage.stageWidth/2, tmph:int = stage.stageHeight/2;
var tmp:Number = 2.0 * Math.PI / NUMBOIDS;
for (i = 0; i < NUMBOIDS; ++i) {
const ph:Number = i * tmp;
b = new Boid(i, tmpw*2, tmph*2,
tmpw + ((i%4) * 0.2 + 0.3)*tmpw * Math.cos(ph),
tmph + ((i%4) * 0.2 + 0.3)*tmph * Math.sin(ph),
((i%4)*(-4) + 16) * Math.cos(ph + Math.PI / 6 * (1+i%4) * (Math.random() - 0.5)),
((i%4)*(-4) + 16) * Math.sin(ph + Math.PI / 6 * (1+i%4) * (Math.random() - 0.5)));
boids[i] = b;
fail[i] = false;
sprites[i] = new Sprite();
//sprites[i].filters = filterset;
var g:Graphics = sprites[i].graphics;
// Draw view range
g.lineStyle(1, 0xffffff);
g.moveTo(0,0);
for (var j:Number = -1; j <= 1; j += 0.01) {
g.lineTo(Math.cos(j * b.myr)*b.perception, Math.sin(j * b.myr)*b.perception);
}
g.lineTo(0,0);
g.drawCircle(0, 0, b.mindist);
//where I'm going
g.moveTo(0, -b.mindist/2-4);
g.lineTo(b.perception, -b.mindist/2-4);
g.lineTo(b.perception, b.mindist/2+4);
g.lineTo(0, b.mindist/2+4);
g.lineStyle(0.1, 0x0055ff);
g.beginFill(0x0055ff);
g.moveTo(4, 0);
g.lineTo(-3, -3);
g.lineTo(-1, 0);
g.lineTo(-3, 3);
g.lineTo(4, 0);
g.endFill();
speeds[i] = new Sprite();
speeds[i].addChild(sprites[i]);
this.addChild(speeds[i]);
failsfield = new TextField();
failsfield.defaultTextFormat = tf;
failsfield.x -= 2;
failsfield.y -= 2;
b.text = failsfield;
speeds[i].addChild(b.text);
}
drawer = new Sprite();
this.addChild(drawer);
stepsfield = new TextField();
stepsfield.defaultTextFormat = tf;
stepsfield.autoSize = "right";
stepsfield.x = stage.stageWidth-5;
this.addChild(stepsfield);
failsfield = new TextField();
failsfield.defaultTextFormat = tf;
failsfield.autoSize = "right";
failsfield.x = stage.stageWidth-5;
failsfield.y = SIZE;
this.addChild(failsfield);
timefield = new TextField();
timefield.defaultTextFormat = tf;
timefield.autoSize = "right";
timefield.x = stage.stageWidth-5;
timefield.y = 2*SIZE;
this.addChild(timefield);
var myTimer:Timer = new Timer(10, 0);
myTimer.addEventListener("timer", timerHandler, false, 99);
do {
fails = 0;
step(false);
} while ((fails > 0) && (steps < 150))
fails = 0;
move = true;
stage.addEventListener(Event.ENTER_FRAME, onEnterFrame, false, 100);
stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
stage.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
myTimer.start();
}
public function onEnterFrame (ev:Event) : void {
redraw();
}
public function onMouseDown (ev:MouseEvent) : void {
move = !move;
}
public function onMouseWheel (ev:MouseEvent) : void {
moves += Math.abs(ev.delta / 3);
}
public function timerHandler (ev:TimerEvent) : void {
timer2 = getTimer();
timefield.text = (timer2 - timer).toString();
timer = timer2;
if (move || moves > 0) {
step();
--moves;
} else if (moves < 0) {
moves = 0;
}
timer2 = getTimer();
timefield.text = "Timers: "+(timer2 - timer).toString() + " (" + timefield.text + ")";
}
public function redraw() : void {
stepsfield.text = "Steps: " + steps.toString();
failsfield.text = "Fails: " + (fails).toString() + " (1/"+(steps/fails).toFixed(0)+" steps)";
for (i = 0; i < NUMBOIDS; ++i) {
b = boids[i];
speeds[i].x = b.px;
speeds[i].y = b.py;
speeds[i].graphics.clear();
speeds[i].graphics.lineStyle(1, 0xFF0000);speeds[i].graphics.moveTo(0, 0);speeds[i].graphics.lineTo(b.vx, b.vy);
speeds[i].graphics.lineStyle(1, 0x00FF00);speeds[i].graphics.moveTo(0, 0);speeds[i].graphics.lineTo(b.ax*40, b.ay*40);
speeds[i].graphics.lineStyle(1, 0xFF00FF);speeds[i].graphics.moveTo(0, 0);speeds[i].graphics.lineTo(b.tmpx*40, b.tmpy*40);
sprites[i].rotation = b.v.angle * 180 / Math.PI;
}
}
public function step(draw:Boolean = true) : void {
++steps;
if (draw)
drawer.graphics.clear();
//select neighbors
for (i = 0; i < boids.length; ++i) {
for (var j:int = i+1; j < boids.length; ++j) {
var dist:Vector3 = boids[i].p.subtract(boids[j].p);
if (dist.lengthSquared < boids[i].mydistSquared) {
if (dist.lengthSquared < boids[i].mindistSquared*4) {
++fails;
//move = false;
}
var angle:Number = Math.abs(boids[i].v.angleBetween(dist));
if (angle < boids[i].myr) {
//add j to i.neighbors
}
angle = Math.abs(boids[j].v.angleBetween(dist));
if (angle < boids[j].myr) {
//add i to j.neighbors
}
}
}
}
for each (b in boids) {
b.force(boids,draw,drawer.graphics);
}
for each (b in boids) {
b.update(draw,drawer.graphics);
}
}
}
}
import flash.display.Graphics;
import flash.display.Sprite;
import flash.display.Stage;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.geom.Vector3D;
function sign(arg:Number):int {
return (arg > 0) ? 1 : ((arg < 0) ? -1 : 0);
}
class Boid {
// parameters
public var maxspeed:Number = 32,
minspeed:Number = 8,
mindist:Number = 8,
perception:Number = 75,
minTime:Number = 10.0/2,
myr:Number = 2/3 * Math.PI,
mindistSquared:int,
mydistSquared:int;
public var text:TextField;
// necessary calculated parameters
private var maxx:int,
maxy:int,
minx:int,
miny:int,
no:int;
// needed variables
private var _p:Vector3,
_v:Vector3,
_a:Vector3,
tmp:Vector3,
rangle:Number;
// variables for functions
// predictTime:
private var relP:Vector3,
relV:Vector3;
// force:
private var _aold:Vector3,
dist:Vector3,
angle:Number,
b:Boid,
time:Number = 0,
mypos:Vector3,
hispos:Vector3,
separation:Vector3,
alignment:Vector3,
cohesion:Vector3,
collision:Vector3,
collisionLen:Number;
//gets:
public function get tmpx():Number { return tmp.x; }
public function get tmpy():Number { return tmp.y; }
public function get px():Number { return _p.x; }
public function get py():Number { return _p.y; }
public function get p():Vector3 { return _p.clone(); }
public function get vx():Number { return _v.x; }
public function get vy():Number { return _v.y; }
public function get v():Vector3 { return _v.clone(); }
public function get ax():Number { return _a.x; }
public function get ay():Number { return _a.y; }
public function get a():Vector3 { return _a.clone(); }
public function get n():int { return no; }
public function Boid(no2:int = 0, maxx2:int = 400, maxy2:int = 400, px:Number = 200, py:Number = 200, vx:Number = 1, vy:Number = 0) {
_p = new Vector3(px, py);
_v = new Vector3(vx, vy);
_a = new Vector3();
tmp = new Vector3();
separation = new Vector3();
alignment = new Vector3();
cohesion = new Vector3();
no = no2;
maxx = maxx2-perception/2;
maxy = maxy2-perception/2;
minx = perception/2;
miny = perception/2;
mindistSquared = mindist*mindist;
mydistSquared = perception*perception;
rangle = Math.random() * 2 * Math.PI;
}
private function predictTime(other:Boid) : Number {
relP = _p.subtract(other.p);
relV = other.v.subtract(_v);
return relV.dotProduct(relP) / relV.lengthSquared;
}
public function force(boids:Array, draw:Boolean, g:Graphics) : void {
var count:int = 0, count2:int = 0;
var minTime:Number = this.minTime;
tmp.reset();
separation.reset();
alignment.reset();
cohesion.reset();
_aold = _a.clone();
_a.reset();
// Unaligned Collision Avoidance
for each (b in boids) {
if (b == this) continue;
dist = b.p.subtract(_p);
//time = predictTime(b);
// Neighborhood seleted by angle i dist
if (dist.lengthSquared < mydistSquared) {
// Collision!
if (dist.lengthSquared < mindistSquared*4) {
if (draw) {
g.lineStyle(1, 0xFF0000);
g.drawCircle(_p.x, _p.y, mindist);
g.beginFill(0xFF0000);
g.drawCircle((_p.x+b.px)/2, (_p.y+b.py)/2, 2);
g.endFill();
if (b.n < this.n)
trace(time.toFixed(4)+" "+(_v.angleBetween(b.v)*180/Math.PI).toFixed(1)+" "+b.n+" "+this.n);
}
}
angle = Math.abs(_v.angleBetween(dist));
if (angle < myr) {
// Separation
separation.decrementBy(dist.scaleBy(perception/dist.lengthSquared));
alignment.incrementBy(b.v);
++count;
//g.lineStyle(1, 0xFF0000, 0.25);
angle = Math.abs(_v.angleBetween(b.v));
if (angle < Math.PI/2) {
cohesion.incrementBy(b.p);//.decrementBy(b.v);
++count2;
//g.lineStyle(1, 0xFFFF00, 0.25);
}
//g.moveTo(_p.x, _p.y);
//g.lineTo(b.p.x, b.p.y);
}
time = predictTime(b);
if ((time < 0.) || (time >= minTime)) continue;
//if (time < 0.) continue;
mypos = v.scaleBy(time).incrementBy(p);
hispos = b.v.scaleBy(time).incrementBy(b.p);
collision = mypos.subtract(hispos);
collisionLen = collision.lengthSquared;
if (collisionLen >= mindistSquared*6.25) continue;
minTime = time;
collisionLen = (1/Math.sqrt(collisionLen));
collision.scaleBy(collisionLen);
//_a.incrementBy(collision);
_a = collision.clone();
text.text = "C";
if (draw) {
g.lineStyle(1, 0x0000FF, 0.4);
g.moveTo(_p.x, _p.y);
g.lineTo(mypos.x, mypos.y);
g.beginFill(0x0000FF, 0.4);
g.drawCircle(mypos.x, mypos.y, mindist);
g.endFill();
}
}
}
/*if (_a.isZero()) {
text.text = "B";
// Boundaries
if (_p.x < minx)
_a.x += 0.4;//0.5*(minx-_p.x)/minx;
else if (_p.x > maxx)
_a.x -= 0.4;//0.5*(_p.x-maxx)/minx;
if (_p.y < miny)
_a.y += 0.4;//0.5*(miny-_p.y)/miny;
else if (_p.y > maxy)
_a.y -= 0.4;//0.5*(_p.y-maxy)/miny;
}*/
if (_a.isAlmostZero()) {
//text.text = "F";
//trace(separation.length);
//_a.incrementBy(separation.scaleBy(0.5));
// Alignment
/*if (count > 0) {
alignment.scaleBy(1/count).decrementBy(_v).scaleBy(1/(maxspeed*2));
_a.incrementBy(alignment.scaleBy(2));
}
// Cohesion
if (count2 > 0) {
cohesion.scaleBy(1/count2).decrementBy(_p).scaleBy(1/(mydist));
_a.incrementBy(cohesion.scaleBy(8));
}*/
}
if (_a.isAlmostZero() && count == 0) {
/*text.text = "W";
// Wandering
rangle += sign(Math.random() - 0.5) * Math.PI / 36;
_a.incrementBy(v.scaleBy(0.44/_v.length));
_a.x += 0.45*Math.sin(rangle);
_a.y += 0.45*Math.cos(rangle);*/
} else {
rangle = _a.angle;
}
// Boundaries
if (_p.x < minx)
_a.x += 0.4;//0.5*(minx-_p.x)/minx;
else if (_p.x > maxx)
_a.x -= 0.4;//0.5*(_p.x-maxx)/minx;
if (_p.y < miny)
_a.y += 0.4;//0.5*(miny-_p.y)/miny;
else if (_p.y > maxy)
_a.y -= 0.4;//0.5*(_p.y-maxy)/miny;
// Acceleration tampering...
_a.decrementBy(_aold);
var t:Number = _a.length;
if (t > 0.0001) _a.scaleBy(0.25);
//max A?
if (t >= 0.15) _a.scaleBy(0.15/t);
// Damping factor (1 -> no damping, 0 -> no change)
_a.scaleBy(0.5);
_a.incrementBy(_aold);
}
public function update(quiet:Boolean, g:Graphics) : void {
_v.incrementBy(_a);
_v.scaleBy(1./12);
_p.incrementBy(_v);
_v.scaleBy(12.);
// speed limit
var v:Number = _v.length;
if (v > maxspeed) {
_v.scaleBy(maxspeed/v);
} else if (v < minspeed) {
_v.scaleBy(minspeed/v);
} else {
_v.scaleBy(0.99);
}
}
}
class Vector3 {
private var _x:Number = 0;
private var _y:Number = 0;
private var _z:Number = 0;
public function get x():Number { return _x; }
public function set x(newx:Number):void { _x = newx; }
public function get y():Number { return _y; }
public function set y(newy:Number):void { _y = newy; }
public function get z():Number { return _z; }
public function set z(newz:Number):void { _z = newz; }
public function get lengthSquared():Number { return x*x+y*y+z*z; }
public function get length():Number { return Math.sqrt(lengthSquared); }
public function get angle():Number { return Math.atan2(_y, _x); }
public function Vector3 (x:Number = 0., y:Number = 0., z:Number = 0.) {
_x = x;
_y = y;
_z = z;
}
public function v3D() : Vector3D {
var ret:Vector3D = new Vector3D(_x, _y, _z);
return ret;
}
public function toString() : String {
return "("+_x.toFixed(2)+", "+_y.toFixed(2)+", "+_z.toFixed(2)+")";
}
public function isNonZero() : Boolean {
if (_x != 0 || _y != 0 || _z != 0) return true;
return false;
}
public function isZero() : Boolean {
if (_x == 0 && _y == 0 && _z == 0) return true;
return false;
}
public function isAlmostZero(min:Number = 0.15999) : Boolean {
if (lengthSquared < min) return true;
return false;
}
public function clone() : Vector3 {
var ret:Vector3 = new Vector3(_x, _y, _z);
return ret;
}
public function reset() : Vector3 {
_x = 0;
_y = 0;
_z = 0;
return this;
}
public function make(arg:Vector3) : Vector3 {
_x = arg.x;
_y = arg.y;
_z = arg.z;
return this;
}
public function incrementBy(arg:Vector3) : Vector3 {
_x += arg.x;
_y += arg.y;
_z += arg.z;
return this;
}
public function decrementBy(arg:Vector3) : Vector3 {
_x -= arg.x;
_y -= arg.y;
_z -= arg.z;
return this;
}
public function add(arg:Vector3) : Vector3 {
var result:Vector3 = new Vector3(_x, _y, _z);
result.x += arg.x;
result.y += arg.y;
result.z += arg.z;
return result;
}
public function subtract(arg:Vector3) : Vector3 {
var result:Vector3 = new Vector3(_x, _y, _z);
result.x -= arg.x;
result.y -= arg.y;
result.z -= arg.z;
return result;
}
public function scaleBy(arg:Number) : Vector3 {
_x *= arg;
_y *= arg;
_z *= arg;
return this;
}
public function scaleTo(arg:Number) : Vector3 {
var tmp:Number = length;
if (tmp == 0) return reset();
return scaleBy(arg/tmp);
}
public function normalize() : Vector3 {
/*var tmp:Number = length;
if (tmp == 0) return reset();
return scaleBy(1./tmp);*/
return scaleTo(1.);
}
public function negate() : Vector3 {
return scaleBy(-1);
}
public function dotProduct(arg:Vector3) : Number {
return _x*arg.x+_y*arg.y+_z*arg.z;
}
public function crossProduct(arg:Vector3):Vector3 {
var result:Vector3 = new Vector3(_y*arg.z-_z*arg.y, _z*arg.x-_x*arg.z, _x*arg.y-_y*arg.x);
return result;
}
public function projectOnto(arg:Vector3):Vector3 {
this.make(arg.clone().scaleBy(dotProduct(arg)/arg.lengthSquared));
return this;
}
public function distance(arg:Vector3):Number {
var result:Vector3 = this.subtract(arg);
return result.length;
}
public function distanceSquared(arg:Vector3):Number {
var result:Vector3 = this.subtract(arg);
return result.lengthSquared;
}
public function rotate(arg:Number):Vector3 {
var tmpx:Number = _x;
var tmpy:Number = _y;
_x = Math.cos(arg)*tmpx - Math.sin(arg)*tmpy;
_y = Math.sin(arg)*tmpx + Math.cos(arg)*tmpy;
return this;
}
public function rotate90():Vector3 {
var tmpx:Number = _x;
_x = -_y;
_y = tmpx;
return this;
}
public function angleBetween(v:Vector3):Number {
var result:Number = Math.atan2(_y, _x) - Math.atan2(v.y, v.x);
if (result < -Math.PI) result += Math.PI*2;
if (result > Math.PI) result -= Math.PI*2;
return result;
}
}
function inittrace(s:Stage): void {
WTrace.initTrace(s);
}
//global trace function
var trace:Function;
//wtreace class
class WTrace {
private static var FONT:String = "Lucida Console";
private static var SIZE:Number = 12;
private static var TextFields:Array = [];
private static var trace_stage:Stage;
public static function initTrace(stg:Stage) : void {
trace_stage = stg;
SIZE = stg.stageHeight/50;
trace = wtrace;
}
private static function scrollup() : void {
// maximum number of lines: 50
if (TextFields.length > 50) {
trace_stage.removeChild(TextFields.shift());
}
for(var x:Number = TextFields.length - 1; x>=0; --x) {
(TextFields[x] as TextField).y -= 1.05*SIZE;
}
}
private static function clear() : void {
while(TextFields.length > 0) {
trace_stage.removeChild(TextFields.shift());
}
}
public static function wtrace(... args) : void {
var s:String="";
var tracefield:TextField;
if (args.length == 0) {
clear();
} else {
/*for (var i:int = 0; i < args.length; ++i) {
if (!(args[i] is int) && args[i] is Number) {
s += args[i].toFixed(3) + " ";*/
var i:String;
for (i in args) {
if (args[i] is Number) {
s += args[i].toFixed(3) + " ";
} else {
s += args[i] + " ";
}
}
tracefield = new TextField();
tracefield.autoSize = "left";
tracefield.text = s;
tracefield.y = trace_stage.stageHeight - SIZE;
var tf:TextFormat = new TextFormat(FONT, SIZE);
tracefield.setTextFormat(tf);
trace_stage.addChild(tracefield);
scrollup();
TextFields.push(tracefield);
}
}
}