package
{
import flash.display.Sprite;
import flash.events.Event;
public class Main extends Sprite
{
private var vehicles:Vector.<SteeredVehicle> = new Vector.<SteeredVehicle>();
public function Main()
{
for (var i:int = 0; i < 30; i++)
{
vehicles.push(addChild(new SteeredVehicle(Math.random() * stage.stageWidth, Math.random() * stage.stageHeight)));
vehicles[i].velocity = new Vector2D(Math.random() * 10 + 10, Math.random() * 10 + 10);
}
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(event:Event):void
{
for each (var vehicle:SteeredVehicle in vehicles)
{
vehicle.flock(vehicles);
vehicle.update();
}
}
}
}
class Vector2D
{
public var x:Number;
public var y:Number;
public function Vector2D(x:Number, y:Number)
{
this.x = x;
this.y = y;
}
public static function get ZERO():Vector2D
{
return new Vector2D(0, 0);
}
public function add(vec:Vector2D):Vector2D
{
this.x += vec.x;
this.y += vec.y;
return this;
}
public function subtract(vec:Vector2D):Vector2D
{
this.x -= vec.x;
this.y -= vec.y;
return this;
}
public function multiply(value:Number):Vector2D
{
this.x *= value;
this.y *= value;
return this;
}
public function divide(value:Number):Vector2D
{
this.x /= value;
this.y /= value;
return this;
}
public function get length():Number
{
return Math.sqrt(x * x + y * y);
}
public function set length(value:Number):void
{
var a:Number = angle;
x = Math.cos(a) * value;
y = Math.sin(a) * value;
}
public function dot(vec:Vector2D):Number
{
return this.x * vec.x + this.y * vec.y;
}
public function normalize(thickness:Number = 1.0):Vector2D
{
if (length == 0)
{
this.x = 1.0;
return this;
}
var scale:Number = thickness / length;
this.x *= scale;
this.y *= scale;
return this;
}
public function distance(vec:Vector2D):Number
{
return Math.sqrt(distanceSquared(vec));
}
public function distanceSquared(vec:Vector2D):Number
{
var tx:Number = this.x - vec.x;
var ty:Number = this.y - vec.y;
return tx * tx + ty * ty;
}
public function truncate(max:Number):Vector2D
{
length = Math.min(length, max);
return this;
}
public function clone():Vector2D
{
return new Vector2D(x, y);
}
public function get angle():Number
{
return Math.atan2(y, x);
}
public function set angle(radian:Number):void
{
var len:Number = length;
x = Math.cos(radian) * len;
y = Math.sin(radian) * len;
}
}
import flash.display.Sprite;
class Vehicle extends Sprite
{
public static const WRAP:String = "wrap";
public static const BOUNCE:String = "bounce";
public var edgeBehavior:String = WRAP;
public var velocity:Vector2D;
public var maxSpeed:Number = 10.0;
public var mass:Number = 1.0;
public function Vehicle(x:Number, y:Number)
{
this.x = x;
this.y = y;
velocity = Vector2D.ZERO;
}
public function update():void
{
velocity.truncate(maxSpeed);
this.x += velocity.x;
this.y += velocity.y;
if (stage)
{
if (edgeBehavior == WRAP) wrap();
else if (edgeBehavior == BOUNCE) bounce();
}
rotation = velocity.angle * 180 / Math.PI;
draw();
}
private function wrap():void
{
if (this.x < 0)
{
this.x = stage.stageWidth - 1;
}
else if (stage.stageWidth <= this.x)
{
this.x = 0;
}
if (this.y < 0)
{
this.y = stage.stageHeight - 1;
}
else if (stage.stageHeight <= this.y)
{
this.y = 0;
}
}
private function bounce():void
{
if (this.x < 0)
{
velocity.x = -velocity.x;
this.x = 0;
}
else if (stage.stageWidth <= this.x)
{
velocity.x = -velocity.x;
this.x = stage.stageWidth - 1;
}
if (this.y < 0)
{
velocity.y = -velocity.y;
this.y = 0;
}
else if (stage.stageHeight <= this.y)
{
velocity.y = -velocity.y;
this.y = stage.stageHeight - 1;
}
}
public function get position():Vector2D
{
return new Vector2D(x, y);
}
protected function draw():void
{
graphics.clear();
graphics.lineStyle(2.0, 0x0);
graphics.moveTo(5, 0);
graphics.lineTo(-10, -5);
graphics.lineTo(-10, 5);
graphics.lineTo(5, 0);
}
}
class SteeredVehicle extends Vehicle
{
public var force:Vector2D;
public var maxForce:Number = 1.0;
public var arriveDist:Number = 100.0;
public var wanderDist:Number = 50.0;
public var wanderRadius:Number = 20.0;
public var wanderAngle:Number = 0.0;
public var wanderAngleRange:Number = 0.5;
public var pathIndex:int = 0;
public var pathThreshold:Number = 10;
public var tooCloseDist:Number = 30.0;
public var inSightDist:Number = 120.0;
public function SteeredVehicle(x:Number, y:Number)
{
super(x, y);
force = Vector2D.ZERO;
}
public function seek(target:Vector2D):void
{
var desiredVelocity:Vector2D = target.subtract(position);
desiredVelocity.normalize().multiply(maxSpeed).subtract(velocity);
force.add(desiredVelocity);
}
public function flee(target:Vector2D):void
{
var desiredVelocity:Vector2D = target.subtract(position);
desiredVelocity.normalize().multiply(maxSpeed).subtract(velocity);
force.subtract(desiredVelocity);
}
public function arrive(target:Vector2D):void
{
var desiredVelocity:Vector2D = target.subtract(position);
var dist:Number = desiredVelocity.length;
desiredVelocity.normalize().multiply(maxSpeed);
if (dist < arriveDist) desiredVelocity.multiply(dist / arriveDist);
desiredVelocity.subtract(velocity);
force.add(desiredVelocity);
}
public function pursue(target:Vehicle):void
{
var frame:Number = target.position.distance(position) / maxSpeed;
var predictedTarget:Vector2D = target.position.clone().add(target.velocity.clone().multiply(frame));
seek(predictedTarget);
}
public function evade(target:Vehicle):void
{
var frame:Number = target.position.distance(position) / maxSpeed;
var predictedTarget:Vector2D = target.position.clone().add(target.velocity.clone().multiply(frame));
flee(predictedTarget);
}
public function wander():void
{
var center:Vector2D = velocity.clone().normalize().multiply(wanderDist);
var offset:Vector2D = Vector2D.ZERO;
offset.length = wanderRadius;
offset.angle = wanderAngle;
center.add(offset).subtract(velocity);
force.add(center);
wanderAngle += Math.random() * wanderAngleRange - wanderAngleRange / 2;
}
public function interpose(vehicle0:Vehicle, vehicle1:Vehicle):void
{
var midpoint:Vector2D = vehicle0.position.clone().add(vehicle1.position).divide(2);
var frame:Number = position.distance(midpoint) / maxSpeed;
var pos0:Vector2D = vehicle0.position.clone().add(vehicle0.velocity.multiply(frame));
var pos1:Vector2D = vehicle1.position.clone().add(vehicle1.velocity.multiply(frame));
midpoint = pos0.add(pos1).divide(2);
arrive(midpoint);
}
public function followPath(path:Vector.<Vector2D>, loop:Boolean = false):void
{
try
{
var wayPoint:Vector2D = path[pathIndex];
}
catch (e:Error) { }
if (wayPoint == null)
{
velocity = Vector2D.ZERO;
return;
}
if (wayPoint.distance(position) < pathThreshold)
{
if (loop && pathIndex >= path.length - 1)
{
pathIndex = 0;
}
else
{
pathIndex++;
}
}
if (!loop && pathIndex == path.length - 1)
{
arrive(wayPoint.clone());
}
else
{
seek(wayPoint.clone());
}
}
public function flock(vehicles:Vector.<SteeredVehicle>):void
{
var avgVelocity:Vector2D = Vector2D.ZERO;
var avgPosition:Vector2D = Vector2D.ZERO;
var count:int = 0;
for each (var vehicle:Vehicle in vehicles)
{
if (vehicle != this && inSight(vehicle))
{
avgVelocity.add(vehicle.velocity);
avgPosition.add(vehicle.position);
count++;
if (position.distance(vehicle.position) < tooCloseDist)
{
flee(vehicle.position);
}
}
}
if (count > 0)
{
avgVelocity.divide(count);
avgPosition.divide(count);
seek(avgPosition);
force.add(avgVelocity.subtract(velocity));
}
}
public function inSight(vehicle:Vehicle):Boolean
{
var diff:Vector2D = vehicle.position.subtract(position);
if (diff.length > inSightDist) return false;
var heading:Vector2D = velocity.clone().normalize();
var dot:Number = diff.dot(heading);
return dot > 0;
}
override public function update():void
{
force.truncate(maxForce);
force.divide(mass);
velocity.add(force);
force = Vector2D.ZERO;
super.update();
}
}