topical media & game development
student-twitter-flock-Boid.ax
student-twitter-flock-Boid.ax
(swf
)
[ flash
]
flex
package {
import flash.events.Event;
import flash.utils.setTimeout;
import mx.containers.Panel;
import mx.controls.Image;
import mx.controls.Label;
public class @ax-student-twitter-flock-Boid {
private var x:int, y:int;
private var maxWidth:int = 900, maxHeight:int = 600, immunityTimer:int = 0;
private var xVelocity:Number, yVelocity:Number, maxVelocity:Number = 5.0,
nestingThreshold:Number, nestingFraction:Number, maxNestingThreshold:Number = 0.2,
minNestingFraction:Number = 0.90, nestingDegradationFactor:Number = 0.9999,
flockingProb:Number = 1.0;
private var image:student_twitter_flock_AnimatedImage, panel:Panel, tweet:student_twitter_flock_Tweet;
private var selected:Boolean = false;
private var similarities:Array;
public function @ax-student-twitter-flock-Boid(image:Image, panel:Panel, tweet:student_twitter_flock_Tweet, anim:Array):void {
addRandomImpulse(2.0);
this.x = random(this.maxWidth);
this.y = random(this.maxHeight);
this.nestingThreshold = this.maxNestingThreshold;
this.nestingFraction = this.minNestingFraction;
this.image = new student_twitter_flock_AnimatedImage(image, anim);
this.tweet = tweet;
this.image.setColor(this.tweet.getColor());
this.panel = panel;
}
public function addRandomImpulse (max:Number):void {
// Sets the boid flying of in a random direction at a random speed.
this.xVelocity = random(max * 2) - max;
this.yVelocity = random(max * 2) - max;
this.nestingFraction = 1;
}
private function random (maxNum:int):int {
return Math.ceil(Math.random() * maxNum);
}
public function calculateSimilarities (boids:Array):void {
// Asks the Tweet to calculate the similarity with all the boids.
this.similarities = new Array(boids.length);
for (var i:int = 0; i < boids.length; i++) {
this.similarities[i] = this.tweet.calculateSimilarity(boids[i].tweet);
}
}
// Interaction
public function hideTweet ():void {
this.image.deselect();
}
public function showTweet (event:Event):void {
setTimeout(showLabel, 50);
}
private function showLabel ():void {
this.panel.title = this.tweet.getScreenName();
var pic:Image = this.panel.getChildByName("profilePic") as Image;
pic.source = this.tweet.getPic();
var label:Label = this.panel.getChildByName("tweetLabel") as Label;
label.text = this.tweet.getText();
var tagLabel:Label = this.panel.getChildByName("moodLabel") as Label;
tagLabel.text = "Moods: " + this.tweet.getMoods();
this.panel.visible = true;
this.image.select();
}
// Render
public function moveImage (screenWidth:int, screenHeight:int):void {
this.image.move(this.x, this.y);
this.image.rotate(direction());
}
public function direction ():Number {
// Determines the angle (in degrees) that the image should be rotated
// to, given the current vector.
var result:Number, distance:Number;
distance = Math.sqrt(this.xVelocity * this.xVelocity + this.yVelocity * this.yVelocity);
result = Math.acos(this.xVelocity/distance);
if (this.yVelocity < 0) {
result *= -1;
}
result *= 180 / Math.PI;
return result;
}
public function getX ():int {
return this.x;
}
public function getY ():int {
return this.y;
}
public function getImage ():Image {
return this.image.getImage();
}
// Boid behaviour
public function move ():void {
// Calculates the new position using the current vector and forces the
// boids to turn around if they get to the edge of the screen.
this.x += this.xVelocity;
this.y += this.yVelocity;
var border:int = 20, width:int = this.maxWidth, height:int = this.maxHeight;
var maxX:int = width;
var minX:int = 0;
var maxY:int = height;
var minY:int = 0;
if (this.x <= minX + border) {
this.xVelocity += 1.0;
this.yVelocity *= (this.yVelocity < 5) ? 1.1 : 1.0;
} else if (this.x >= maxX - border) {
this.xVelocity -= 1.0;
this.yVelocity *= (this.yVelocity < 5) ? 1.1 : 1.0;
}
if (this.y <= minY + border) {
this.yVelocity += 1.0;
this.xVelocity *= (this.xVelocity < 5) ? 1.1 : 1.0;
} else if (this.y >= maxY - border) {
this.yVelocity -= 1.0;
this.xVelocity *= (this.xVelocity < 5) ? 1.1 : 1.0;
}
if (this.x < minX - 2 * border || this.x > (maxX + 2 * border) ||
this.y < minY - 2 * border || this.y > (maxY + 2 * border)) {
this.x = 100;
this.y = 100;
addRandomImpulse(2.0);
}
}
public function distance (boid:student_twitter_flock_Boid):int {
var distX:int = this.x - boid.x,
distY:int = this.y - boid.y;
return Math.sqrt(distX * distX + distY * distY);
}
public function separation (boids:Array, minDistance:int):void {
// This algorithm pushes the boids away from each other.
var distanceX:int = 0,
distanceY:int = 0,
numClose:int = 0;
for(var i:int = 0; i < boids.length; i++) {
var boid:student_twitter_flock_Boid = boids[i];
var similarityFactor:Number = (1 - this.flockingProb) + this.flockingProb * this.similarities[i];
if(boid.x == this.x && boid.y == this.y) continue;
var distance:int = this.distance(boid)
if(distance < minDistance) {
numClose++;
var xdiff:int = (this.x - boid.x),
ydiff:int = (this.y - boid.y);
if (xdiff >= 0) xdiff = Math.sqrt(minDistance) - xdiff;
else if (xdiff < 0) xdiff = -Math.sqrt(minDistance) - xdiff;
if (ydiff >= 0) ydiff = Math.sqrt(minDistance) - ydiff;
else if (ydiff < 0) ydiff = -Math.sqrt(minDistance) - ydiff;
distanceX += xdiff * similarityFactor;
distanceY += ydiff * similarityFactor;
boid = null;
}
}
if(numClose == 0) return;
this.xVelocity -= distanceX / 5;
this.yVelocity -= distanceY / 5;
}
public function cohesion (boids:Array, distance:int):void {
// This algorithm brings the boids towards each other.
if(boids.length < 1) return;
// Calculate the average velocity of the other boids
var avgX:int = 0, avgY:int = 0, avgZ:int = 0;
for(var i:int = 0; i < boids.length; i++) {
var boid:student_twitter_flock_Boid = boids[i];
if(boid.x == this.x && boid.y == this.y) continue;
if(this.distance(boid) > distance) continue;
var similarityFactor:Number = (1 - this.flockingProb) + this.flockingProb * this.similarities[i];
avgX += (this.x - boid.x) * similarityFactor;
avgY += (this.y - boid.y) * similarityFactor;
boid = null;
}
avgX /= boids.length;
avgY /= boids.length;
avgZ /= boids.length;
distance = Math.sqrt((avgX * avgX) + (avgY * avgY) + (avgZ * avgZ)) * -1.0;
if(distance == 0) return;
this.xVelocity = Math.min(this.xVelocity + (avgX / distance) * 0.15, this.maxVelocity);
this.yVelocity = Math.min(this.yVelocity + (avgY / distance) * 0.15, this.maxVelocity);
}
public function alignment (boids:Array, distance:int):void {
// This algorithm makes the boid turn toward the same
// direction as the average of the boids around it.
if(boids.length < 1) return;
// Calculate the average velocity of the other boids
var avgX:int = 0, avgY:int = 0, avgZ:int = 0;
for(var i:int = 0; i < boids.length; i++) {
var boid:student_twitter_flock_Boid = boids[i];
if(boid.x == this.x && boid.y == this.y) continue;
if(this.distance(boid) > distance) continue;
var similarityFactor:Number = (1 - this.flockingProb) + this.flockingProb * this.similarities[i];
avgX += boid.xVelocity * similarityFactor;
avgY += boid.yVelocity * similarityFactor;
boid = null;
}
avgX /= boids.length;
avgY /= boids.length;
avgZ /= boids.length;
distance = Math.sqrt((avgX * avgX) + (avgY * avgY) + (avgZ * avgZ)) * 1.0;
if(distance == 0) return;
this.xVelocity = Math.min(this.xVelocity + (avgX / distance) * 0.05, this.maxVelocity);
this.yVelocity = Math.min(this.yVelocity + (avgY / distance) * 0.05, this.maxVelocity);
}
public function treeSeeking (trees:Array, distance:int):void {
// This algorithm makes the boid turn toward the same
// direction as the average of the trees around it.
if(trees.length < 1) return;
// calculate the average direction of the trees that are in range
var diffX:int = 0, diffY:int = 0;
for(var i:int = 0; i < trees.length; i++) {
var tree:student_twitter_flock_Tree = trees[i];
if(tree.distanceToBoid(this) > distance) continue;
diffX += (tree.getX() - this.x) / distance;
diffY += (tree.getY() - this.y) / distance;
tree = null;
}
var dist:Number = Math.sqrt((diffX * diffX) * 1.0 + (diffY * diffY) * 1.0);
if (dist == 0) return;
this.xVelocity = Math.min(this.xVelocity + (diffX / dist) * 0.05, this.maxVelocity);
this.yVelocity = Math.min(this.yVelocity + (diffY / dist) * 0.05, this.maxVelocity);
}
public function nesting (trees:Array):void {
// This algorithm slowes down the boid when it is over a tree.
// If it sits on the tree for a while the bird will become immune
// to the effects of this algorithm.
var onTree:Boolean = false;
if(trees.length < 1) return;
for(var i:int = 0; i < trees.length; i++) {
var tree:student_twitter_flock_Tree = trees[i];
if(tree.distanceToBoid(this) < tree.getRadius() - 10) {
onTree = true;
if (immunityTimer == 0) {
if (this.xVelocity * this.xVelocity > this.nestingThreshold) {
this.xVelocity *= this.nestingFraction;
} else {
this.xVelocity = 0;
}
if (this.yVelocity * this.yVelocity > this.nestingThreshold) {
this.yVelocity *= this.nestingFraction;
} else {
this.yVelocity = 0;
}
if (this.xVelocity == 0 && this.yVelocity == 0) {
this.immunityTimer = 200;
}
if (this.nestingThreshold > 0.05) {
this.nestingThreshold *= this.nestingDegradationFactor;
} else {
this.nestingThreshold = 0;
}
if (this.nestingFraction < 0.995) {
this.nestingFraction /= this.nestingDegradationFactor;
} else {
this.nestingFraction = 1;
}
}
} else {
this.immunityTimer = (this.immunityTimer > 0) ? this.immunityTimer - 1 : 0;
if (this.nestingThreshold < this.maxNestingThreshold) {
this.nestingThreshold /= this.nestingDegradationFactor;
} else {
this.nestingThreshold = this.maxNestingThreshold;
}
if (this.nestingFraction < this.minNestingFraction) {
this.nestingFraction *= this.nestingDegradationFactor;
} else {
this.nestingFraction = this.minNestingFraction;
}
}
}
if (!onTree && this.xVelocity == 0 && this.yVelocity == 0) {
this.addRandomImpulse(3.0);
}
}
}
}
(C) Æliens
20/2/2008
You may not copy or print any of this material without explicit permission of the author or the publisher.
In case of other copyright issues, contact the author.