package turtlekit.flocking;
import java.awt.Color;
import java.util.List;
import turtlekit.kernel.Turtle;
/**
* BirdFlocking represents the "Bird" of the simulation
* <p>
* A "Bird" agent is characterized by:
* <ul>
* <li>A Field of View</li>
* <li>A Speed</li>
* <li>Rotation Angles</li>
* <li>A neighbor list</li>
* </ul>
* </p>
*
* @author Emmanuel Hermellin
*
* @version 0.1
*
* @see turtlekit.kernel.Turtle
*
*/
public class BirdFlockingUnify extends Turtle {
/**
* The Speed
* @see BirdFlockingCPU#activate()
* @see BirdFlockingCPU#move()
*/
private float speed = 1.0f;
/**
* An adaptative speed value
* @see BirdFlockingCPU#align()
* @see BirdFlockingCPU#cohere()
*/
private float adapatativeSpeed = 0.1f;
/**
* The nearest neighbor
* @see BirdFlockingCPU#flock()
* @see BirdFlockingCPU#separate()
* @see BirdFlockingCPU#align()
*/
private BirdFlockingUnify nearestBird;
/**
* The neighbor list
* @see BirdFlockingCPU#flock()
* @see BirdFlockingCPU#cohere()
*/
private List<BirdFlockingUnify> birdList;
/**
* Return the Field of View
* @return vision
*/
public static int getVision() {
return FlockingModel.vision;
}
/**
* Return the Speed
* @return speed
*/
public float getSpeed() {
return speed;
}
/**
* Set the Speed of the agent
* @param speed
* The new Speed
*
* @see BirdFlockingCPU#align()
* @see BirdFlockingCPU#cohere()
*/
public void setSpeed(float speed) {
this.speed = speed;
}
/**
* Activate the agent
* @see Turtle#activate()
*/
protected void activate() {
super.activate();
setColor(Color.YELLOW);
moveTo(generator.nextInt(getWorldHeight()),
generator.nextInt(getWorldWidth()));
home();
randomHeading();
speed = generator.nextFloat() + 0.5f;
adapatativeSpeed = speed / 5.0f;
setNextAction("flock");
}
/**
* Agent behavior : move
*/
public void move() {
fd(speed);
}
/**
* Agent behavior : global behavior
* According to the distance between the agents, the different Reynolds's rules will be activated
*/
public String flock() {
birdList = getPatch().getTurtles(FlockingModel.vision, false, BirdFlockingUnify.class);
nearestBird = null;
if (! birdList.isEmpty()) {
nearestBird = birdList.get(0);
}
if (nearestBird == null || birdList.isEmpty()) {
move();
fillHeadingEnvironment(this.getHeading());
return "flock";
}
if (this.distance(nearestBird) < FlockingModel.minSeparation) {
return "separate";
}
else {
if (birdList.size() > 5) {
return "cohere";
} else {
return "align";
}
}
}
/**
* Agent behavior : separate
* If agents are too close, they separate
*/
public String separate() {
this.setColor(Color.RED);
double headingInterseptionNearestBird = this.towards(nearestBird);
if (amIInTheInterval(this.getHeading(), headingInterseptionNearestBird, FlockingModel.maxSeparateTurn*2)) {
this.setHeading(changeHeading(this.getHeading(), FlockingModel.maxSeparateTurn));
}
adaptSpeed(nearestBird.getSpeed() + generator.nextFloat());
move();
fillHeadingEnvironment(this.getHeading());// Avant double myHeading = this.getHeading()
return "flock";
}
/**
* Agent behavior : align
* The agent searches for align its movement to its neighbor
*/
public String align() {
this.setColor(Color.BLUE);
double otherHeading = nearestBird.getHeading();
if (!amIInTheInterval(this.getHeading(), otherHeading, 1)) {
this.setHeading(changeHeadingReduceInterval(this.getHeading(),otherHeading,FlockingModel.maxAlignTurn));
}
adaptSpeed(nearestBird.getSpeed());
move();
fillHeadingEnvironment(this.getHeading());// Avant double myHeading = this.getHeading()
return "flock";
}
/**
* Agent behavior : cohesion
* The agents try to make a group during their movement
*/
public String cohere() {
float globalHeading = 0;
int size = getOtherTurtles(0, true).size();
if(size > 0){
this.setColor(Color.WHITE);
}
else{
this.setColor(Color.GREEN);
}
if(!Environment.isCUDA()){
float globalSpeed = 0;
for(BirdFlockingUnify bird : birdList){
globalHeading += bird.getHeading();
globalSpeed += bird.getSpeed();
}
globalHeading = globalHeading / birdList.size();
globalSpeed = globalSpeed / birdList.size();
adaptSpeed(globalSpeed);
}
else{
globalHeading = ((Environment) getEnvironment()).getCudaHeadingValue(this.xcor(), this.ycor());
}
// if (myHeading > globalHeading) {
// myHeading = myHeading - generator.nextInt(maxCohereTurn);
// } else if (myHeading < globalHeading) {
// myHeading = myHeading + generator.nextInt(maxCohereTurn);
// } else {
// myHeading = globalHeading;
// }
//
// this.setHeading(myHeading);
if (!amIInTheInterval(this.getHeading(), globalHeading, 1)) {
this.setHeading(changeHeadingReduceInterval(this.getHeading(),globalHeading,FlockingModel.maxCohereTurn));
}
move();
fillHeadingEnvironment(this.getHeading());
int currentBehaviorCount = getCurrentBehaviorCount();
if (currentBehaviorCount > 10) {
return "flock";
}
return "cohere";
}
/**
* Random heading for the agent
*/
public void headingChange() {
randomHeading();
}
/**
* Fill its heading value in the environment
*/
public void fillHeadingEnvironment(double heading){
((Environment) getEnvironment()).setCudaHeadingValue(this.xcor(), this.ycor(), heading);
}
/**
* Random heading for the agent
*/
public double changeHeading(double heading, int turn) {
if (generator.nextBoolean()) {
return heading = heading + generator.nextInt(turn);
} else {
return heading = heading - generator.nextInt(turn);
}
}
/**
* Test pour connaitre si je suis dans l'intervalle
*/
public boolean amIInTheInterval(double myHeading, double otherHeading, int interval){
double diffTwoAngle = differenceTwoAngle(myHeading, otherHeading);
return (diffTwoAngle <= interval);
}
/**
* Connaitre la différence entre deux angles (sans prise en compte des signes)
*/
public double differenceTwoAngle(double targetA, double targetB){
double d = Math.abs(targetA - targetB) % 360;
return d > 180 ? 360 - d : d;
}
/**
* Adapter la direction en fonction de la différence d'angle entre l'agent et la cible
*/
public double changeHeadingReduceInterval(double myHeading, double otherHeading, int turn){
// //GOOD for GPU
double differenceAngle = differenceTwoAngle(myHeading, otherHeading);
int turnAngle = generator.nextInt(turn);
double temp = differenceTwoAngle((myHeading + turnAngle), otherHeading);
if(temp > differenceAngle){
return myHeading = myHeading - turnAngle;
}
else{
return myHeading = myHeading + turnAngle;
}
// Good for CPU
// double differenceAngle = differenceTwoAngle(myHeading, otherHeading);
// int turnAngle = generator.nextInt(turn);
// while (turnAngle > differenceAngle){
// turnAngle = generator.nextInt(turn);
// }
// double tempP = differenceTwoAngle((myHeading + turnAngle), otherHeading);
// double tempM = differenceTwoAngle((myHeading - turnAngle), otherHeading);
//
// if(differenceAngle == 0){
// return myHeading;
// }
// else {
// if(tempP > tempM){
// return myHeading = myHeading - turnAngle;
// }
// else{
// return myHeading = myHeading + turnAngle;
// }
// }
}
/**
* Connaitre la différence entre deux angles (avec prise en compte des signes)
*/
public double differenceTwoAngleV2(double myHeading, double otherHeading){
double a = (otherHeading - myHeading) % 360;
if(a < -180) a += 360;
if(a > 180) a -= 360;
return a;
}
/**
* Adapter la direction en fonction de la différence d'angle entre l'agent et la cible (en fonction du signe de la diff d'angle)
*/
public double changeHeadingReduceIntervalV2(double myHeading, double otherHeading, int turn){
double differenceAngle = differenceTwoAngleV2(myHeading, otherHeading);
int turnAngle = generator.nextInt(turn);
if(differenceAngle > 0){
return myHeading = myHeading - turnAngle;
}
else{
return myHeading = myHeading + turnAngle;
}
}
/**
* Adapt the speed
*/
public void adaptSpeed(float comparativeSpeed) {
float currentSpeed = this.getSpeed();
if (currentSpeed > FlockingModel.maxSpeed) {
currentSpeed = currentSpeed - adapatativeSpeed;
} else if (currentSpeed < FlockingModel.minSpeed) {
currentSpeed = currentSpeed + adapatativeSpeed;
} else {
if (currentSpeed > comparativeSpeed) {
currentSpeed = currentSpeed - adapatativeSpeed;
} else {
currentSpeed = currentSpeed + adapatativeSpeed;
}
}
this.setSpeed(currentSpeed);
}
}