package { import flash.events.Event; import flash.utils.setTimeout; import mx.containers.Panel; import mx.controls.Image; import mx.controls.Label; public class student_twitter_swarm_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_swarm_AnimatedImage, panel:Panel, tweet:student_twitter_swarm_Tweet; private var selected:Boolean = false; private var similarities:Array; public function student_twitter_swarm_Boid(image:Image, panel:Panel, tweet:student_twitter_swarm_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_swarm_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_swarm_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_swarm_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_swarm_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_swarm_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_swarm_Tree = trees[i]; if ( this.tweet.containsMood(tree.getMood()) ) { if(tree.distanceToBoid(this) > distance) continue; diffX += (tree.getX() - this.x); diffY += (tree.getY() - this.y); } 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_swarm_Tree = trees[i]; if ( this.tweet.containsMood(tree.getMood()) ) { 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; } } } } } } }