//Libraries
//import processing.opengl.*;
import peasy.*;
//Global variables
float align = 3; // Distance for Flockers to follow each other.
float avoid = 2; // Distance to move away from each other
float cohesion = 2.5; // A factor to keep the Flockers crowding
int startingFlockers = 2; // A number that determines the initial number of agents
int maxFlockers = 6; // Maximum number of Flockers to be generated
float minDist = 46; // The local area for the Flockers to be aware of
float multiplier = 50; // an adjustment multiplier
PeasyCam cam; // The camera to be used
public PVector velocity; // A public vector to transfer values and draw with them
public int depth = 800; // To determine the depth of the block to calculate
public int cellSize = 200; // A measure for the onscreen grid
public int Rows, Cols, Stacks, Cells; // Integers to store the number of gridcells per axis
ArrayList[] FlockersCollection; // The array list to store the arrays of flockers
ArrayList[] VoxelCollection; // The array list to store the arrays of voxels
public int speedLimit = 5; // To limit the speed for the agents to run
public int flockerCounter = 0; // To keep the track on how many agents are flocking around
public int maxAge = 1000; // To set maximum time (in frames) for the agents to be on screen
public boolean MouseSwitch = true; // To hide the HUD display
float noiseScale = 0.1; // Add some noise by using a controlled randomness
public boolean beginHatch = true; // To trigger the Hatch behaviour under certain circumstances
public float maxFsize = 5; // Maximum Flocker size
public float minFsize = 0.1; // Minimum Flocker size
int Vfalloff = 10; // A falloff value to control the Voxel variation
public int VoxelSize = 10; // A size value per voxel
int VoxelNumber; // To store the number of voxels
public float totalFood=0;
public float FmaxEnergy = 300; // The limit of energy per Flocker
public float replenishRate = 0.4; // To store the food erplenish abillity
///////////////////////////////////////////////////////////////////////////////////////
// SETUP /////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
void setup() {
size(800, 800, P3D); //sets the size and renderer for the process.
//size(depth,depth,OPENGL);//I'm using the default Processing 3D to be able to share the sketches.
// But the final results will be rendered with OpenGL to achieve better results (like round points)
// Don't use anti-alias to render all the geometries yet.
noSmooth();
cam = new PeasyCam(this, width/2, height/2, depth/2, depth*1.25); //generate the camera
//define the array to put agents in
Rows = (width / cellSize);
Cols = (height / cellSize);
Stacks = (depth / cellSize);
Cells = (Rows*Cols*Stacks); // The total number of cells
VoxelNumber = int(pow((depth/VoxelSize), 3));
FlockersCollection = new ArrayList[Cells];
VoxelCollection = new ArrayList[Cells];
///////////////////////////////////////////////
//// Initialise the flockers array cells /////
/////////////////////////////////////////////
for (int i = 0; i<= FlockersCollection.length-1; i++) {
FlockersCollection[i] = new ArrayList();
}
// Generate the agents and put them into the cells
for (int i=0; i < startingFlockers; i++) {
generateAgent(0.1);
}
/////////////////////////////////////////////
//// Initialise the voxels array cells /////
///////////////////////////////////////////
// Generate the Voxels and put them into the cells
for (int i = 0; i<= VoxelCollection.length-1; i++) {
VoxelCollection[i] = new ArrayList();
}
// Generate the agents and put them into the cells
for (int i = 0; i < (width/VoxelSize); i++) {
for (int j = 0; j < (height/VoxelSize); j++) {
for (int k = 0; k < (depth/VoxelSize); k++) {
PVector Vloc = new PVector (i*VoxelSize, j*VoxelSize, k*VoxelSize);
generateVoxel(Vloc);
}
}
}
}
///////////////////////////////////////////////////////////////////////////////////////
// DRAW /////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
void draw() {
////// The good old fashioned black background //////
background(0);
////////////////////////////////////////////////////////
// Generate the hatching agents /////////////////////// Stage 1 behaviour: Hatch
//////////////////////////////////////////////////////
if (flockerCounter < maxFlockers && beginHatch == true) { // Check
int n=0;
if (n < (maxFlockers-flockerCounter)) { //Changed a for loop to show the hatching process
generateAgent(0.2);
n++;
}
}
////////////////////////////////////////////////////////
// Kill the old agents //////////////////////////////// Stage 1 behaviour: Die
//////////////////////////////////////////////////////
Die();
////////////////////////////////////////////////////////////
////// DRAW THE FLOCKERS AND RUN THE BEHAVIOURS INCLUDED //
for (ArrayList FlockerPop : FlockersCollection) {
for (int i = 0; i < FlockerPop.size(); i ++ ) {
Flocker F = (Flocker)FlockerPop.get(i);
F.run();
}
}
////////////////////////////////////////////////////////////
////// DRAW THE VOXELS AND RUN THE BEHAVIOURS INCLUDED ////
for (ArrayList VoxelPop : VoxelCollection) {
for (int i = 0; i < VoxelPop.size(); i ++ ) {
Voxel V = (Voxel)VoxelPop.get(i);
V.run();
}
}
Message();
if (flockerCounter<maxFlockers*0.75 || flockerCounter==maxFlockers) beginHatch = !beginHatch;
}
///////////////////////////////////////////////////////////////////////////////////////
// FUNCTIONS ////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
////// GENERATE THE AGENTS, AND FILL THE ARRAYLISTS /////// Stage 1 behaviour: Hatch
//////////////////////////////////////////////////////////
void generateAgent(float zone) {
//Define the variables to use: Position only
float AlocX = (random(width*(0.5-zone), width*(0.5+zone)));
float AlocY = (random(height*(0.5-zone), height*(0.5+zone)));
float AlocZ = (random(depth*(0.5-zone), depth*(0.5+zone)));
//Check the cell containing the agent and assign it there
int XCell = floor(AlocX / cellSize);
int YCell = floor(AlocY / cellSize);
int ZCell = floor(AlocZ / cellSize);
//Calculate the cell number
int Cell = (XCell + (YCell*Rows) + (ZCell*Cols*Rows));
//Define the location vector
PVector Aloc = new PVector (AlocX, AlocY, AlocZ);
//Generate Flockers and store them within the array (FlockerPop)
Flocker F = new Flocker(
//Variables incorporated
Aloc
);
FlockersCollection[Cell].add(F);
flockerCounter++;
}
////////////////////////////////////////////////////////////
////// REMOVE THE OVER AGED AGENTS //////////////////////// Stage 1 behaviour: Die
//////////////////////////////////////////////////////////
void Die() {
for (int i = 0; i<= FlockersCollection.length-1; i++) {
for (int j = 0; j<FlockersCollection[i].size(); j++) {
Flocker Aging = (Flocker) FlockersCollection[i].get(j);
if (Aging.Fage > maxAge) {
FlockersCollection[i].remove(Aging);
flockerCounter--;
}
if (Aging.Fsize <= minFsize && Aging.Fage > maxAge/2 && frameCount%3 ==0) {
FlockersCollection[i].remove(Aging);
flockerCounter--;
}
}
}
}
////////////////////////////////////////////////////////////
////// GENERATE THE VOXEL SPACE, AND FILL THE ARRAYLISTS // Stage 3
//////////////////////////////////////////////////////////
void generateVoxel(PVector Loc) {
//Define the variables to use: Position only
float VlocX = Loc.x;
float VlocY = Loc.y;
float VlocZ = Loc.z;
//Check the cell containing the agent and assign it there
int XCell = floor(VlocX / cellSize);
int YCell = floor(VlocY / cellSize);
int ZCell = floor(VlocZ / cellSize);
//Calculate the cell number
int Cell = (XCell + (YCell*Rows) + (ZCell*Cols*Rows));
//Generate Voxels and store them within the array (FlockerPop)
Voxel V = new Voxel(
//Variables incorporated
Loc
);
VoxelCollection[Cell].add(V);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CLASS: AGENT.....................................................................///////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////
public class Flocker {
/////// INSTANCE VARIABLES //////
// To store position
PVector Floc;
//To store velocity
PVector Fvel = new PVector(random(-speedLimit, speedLimit), random(-speedLimit, speedLimit), random(-speedLimit, speedLimit));
// To store the acceleration, responsable for the steering behaviour
PVector Facc = new PVector (0, 0, 0);
//to designate the arrays
int lastCell, newCell;
//To designate the agent's lifetime
float Fage;
//To designate the agent's size
float Fsize=minFsize+0.1;
// To manage the Shriking behaviour strenght
float sDecrease = 0.1;
// To store how much energy the agent has
public float Fenergy;
// To manage how the agent uses energy
public float EnergyConsumption;
//////////////////////////////////////////////////////////////////////////////////////////////////////
////// CONSTRUCTOR -----------------------------------------------------------///////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
Flocker(
// Position
PVector _Floc
)
{
// Push variables to local
Floc = _Floc;
//Find the cell containing the Flocker
int XCell = floor(Floc.x/cellSize);
int YCell = floor(Floc.y/cellSize);
int ZCell = floor(Floc.z/cellSize);
//Check the range and correct it if necessary
if (XCell > Rows) XCell = Rows;
if (YCell > Cols) YCell = Cols;
if (ZCell > Stacks) ZCell = Stacks;
if (XCell < 1) XCell = 0;
if (YCell < 1) YCell = 0;
if (ZCell < 1) ZCell = 0;
//Enumerate the corresponding cell
lastCell = (XCell + (YCell*Rows) + (ZCell*Cols*Rows));
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
////// FUNCTIONS ------------------------------------------------------------------//////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
// RUN:calls what the flocker will do /////////////////////////////////////////////
void run() {
if (random(100)<50) flockIt(); // Move the object with local awarenes
updatePos(); // Keep the Flocker on the working area
display(); // Draw the Flocker
updateCell();
Age();
evolve();
Feed();
}
////////////////////////////////////////////////////////////////////////////////////
// EAT: Get energy from the field /////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////Stage 3 behaviour: Eat
//Still at work
public void Feed() {
if (Fenergy < FmaxEnergy*.5) {
// Roll over the cell and check the Flockers within
for (int j = 0; j < FlockersCollection[lastCell].size(); j++) {
Voxel V = (Voxel) VoxelCollection[lastCell].get(j);
if (V.VfoodConsumption > 0.5) {
//Find the distance to the voxel
float distance = PVector.dist(Floc, V.Vloc); // Get the distance to the voxel
if (distance < 200) {// If the voxel is within range
//Find out how much energy the actual voxel has
Fenergy = Fenergy + V.VfoodConsumption; // And consume it
if(Fenergy > 0) print("Fenergy["+j+"]: " + Fenergy);
}
}
}
}
else Fenergy = Fenergy - EnergyConsumption; // Otherwise, consume your own.
if (Fenergy < 0) Fenergy = 0; // Verify the energy never gets below zero.
}
////////////////////////////////////////////////////////////////////////////////////////
// EVOLVE: according to the amount of energy the agent have, grow or shrink ///////////
//////////////////////////////////////////////////////////////////////////////////////Stage 3 behaviour: Consume
void evolve() {
/////////////////////////////////////////////////////////////////////////////
// GROW: under defined conditions, the agent increases its size ////////////Stage 2 behaviour: Grow
if (Fsize < maxFsize && Fage < maxAge*.75) {
Fsize = map((Fenergy), 0, ((FmaxEnergy)), 0, maxFsize);
}
if (Fsize > maxFsize || Fsize < -maxFsize) Fsize = maxFsize;
/////////////////////////////////////////////////////////////////////////////
// SHRINK: as the agent's life consumes, it decreases its size ///////////// Stage 2 behaviour: Shrink
if (Fsize > minFsize && Fage > maxAge*.75) {
sDecrease=sDecrease+ (maxFsize / maxAge/10);
Fsize = Fsize-sDecrease;
}
if (Fsize < minFsize || Fsize > -minFsize) Fsize = minFsize;
}
/////////////////////////////////////////////////////////////////////////////
// AGE: The longest algorithm ////////////////////////////////////////////// Stage 2 behaviour: Grow
void Age() {
Fage=Fage+random(3);
}
/////////////////////////////////////////////////////////////////////////////
// UPDATECELL : Calculate and update which cell the agent is into //////////
void updateCell() {
// Check the location and calculate the corresponding grid space
// Find the cell containing the Flocker
int XCell = floor(Floc.x/cellSize);
int YCell = floor(Floc.y/cellSize);
int ZCell = floor(Floc.z/cellSize);
// Check the range and correct it if necessary
if (XCell > Rows) XCell = Rows;
if (YCell > Cols) YCell = Cols;
if (ZCell > Stacks) ZCell = Stacks;
if (XCell < 1) XCell = 0;
if (YCell < 1) YCell = 0;
if (ZCell < 1) ZCell = 0;
// Enumerate the corresponding cell
newCell = (XCell + (YCell*Rows) + (ZCell*Cols*Rows));
if (newCell < 1) newCell = 1;
if (newCell >= Cells) newCell = Cells-1;
// Check if there has been a change in the grid cell allocation
if (newCell != lastCell) {
// Remove the agent from current list and add to new list
int index = FlockersCollection[lastCell].indexOf(this);
FlockersCollection[lastCell].remove(index);
FlockersCollection[newCell].add(this);
//update lastCell value
lastCell = newCell;
}
}
////////////////////////////////////////////////////////////////////////////////////
///////// FLOCK: Moves the agent around /////////////////////////////////////////// Stage 1 behaviour: Move
void flockIt() {
for (int j = 0; j < FlockersCollection[newCell].size(); j++) {
Flocker F = (Flocker) FlockersCollection[newCell].get(j);
float FlockerDist = PVector.dist(Floc, F.Floc);
if (FlockerDist > 0) {
if (FlockerDist < minDist) {
if (FlockerDist < align*multiplier*Fsize) {
PVector addVec = F.Fvel.get();
addVec.mult(FlockerDist);
Facc.add(addVec);
Facc.normalize();
}
if (FlockerDist < avoid*multiplier*Fsize) {
PVector addVec = F.Floc.get();
addVec.mult(2);
addVec.sub(Floc);
addVec.mult(FlockerDist);
Facc.sub(addVec);
Facc.normalize();
}
if (FlockerDist < cohesion*multiplier*Fsize) {
PVector addVec = F.Floc.get();
addVec.add(Floc);
addVec.mult(2/FlockerDist);
Facc.add(addVec);
Facc.normalize();
}
}
}
}
}
////////////////////////////////////////////////////////////////////////////////////
////// UPDATEPOS: Updates the location of the Flocker and kepps it in range /////// Stage 1 behaviour: Move
void updatePos() {
// In case the agent is still, move it
if (Fvel.mag()<0.20) Fvel = new PVector(random(-speedLimit, speedLimit/2), random(-speedLimit, speedLimit/2), random(-speedLimit, speedLimit/2));
//Produce the steering behaviour, responsable for the dampened changes
Fvel.add(Facc);
//Limit the speed with graphic purposes
Fvel.limit(speedLimit);
velocity = Fvel;
Fvel.mult(noise(noiseScale));
Floc.add(Fvel);
//if the X coord is out of the boundary, move it to the other boundary
if (Floc.x > width) {
Floc.x = 0;
}
if (Floc.x < 0) {
Floc.x = width;
}
//if the Y coord is out of the boundary, move it to the other boundary
if (Floc.y > height) {
Floc.y = 0;
}
if (Floc.y < 0) {
Floc.y = height;
}
//if the Z coord is out of the boundary, move it to the other boundary
if (Floc.z > depth) {
Floc.z = 0;
}
if (Floc.z < 0) {
Floc.z = depth;
}
}
////////////////////////////////////////////////////////////////////////////////////
////// DISPLAY: renders the shape to the Flocker //////////////////////////////////
void display() {
//Use the current position to map zones by colours
color Col = color(int(map(velocity.x, 0, speedLimit, 70, 25)), int(map(velocity.y, 0, speedLimit, 150, 200)), int(map(velocity.z, 0, speedLimit, 200, 240)));
//Draw a point
strokeWeight(Fsize);
point(Floc.x, Floc.y, Floc.z);
PVector end = new PVector(Floc.x, Floc.y, Floc.z);
velocity.mult(4*Fsize);
end.sub(velocity);
//Draw a line which represents the direction of the Flocker
strokeWeight(1);
line(Floc.x, Floc.y, Floc.z, end.x, end.y, end.z);
pushMatrix();
noFill();
translate (Floc.x, Floc.y, Floc.z);
stroke(Col, 100);
ellipse(0, 0, multiplier*align*Fsize, multiplier*align*Fsize);
stroke(Col, 200);
ellipse(0, 0, multiplier*cohesion*Fsize, multiplier*cohesion*Fsize);
stroke(Col, 255);
ellipse(0, 0, multiplier*avoid*Fsize, multiplier*avoid*Fsize);
popMatrix();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CLASS: VOXEL..........................................................................................//
//////////////////////////////////////////////////////////////////////////////////////////////////////////
public class Voxel {
/////// INSTANCE VARIABLES //////
// To store position
PVector Vloc;
// To store a color value
public float VfoodConsumption;
// To store the cell housing the voxel
int cellNum;
// To store the distance from voxel to flocker
public float FtotalDist;
//////////////////////////////////////////////////////////////////////////////////////////////////////
////// CONSTRUCTOR -----------------------------------------------------------///////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
Voxel (
// Position
PVector _Vloc
) {
// Push variables to local
Vloc =_Vloc;
// Vsize =_Vsize;
//Find the cell containing the Voxel
int XCell = floor(Vloc.x/cellSize);
int YCell = floor(Vloc.y/cellSize);
int ZCell = floor(Vloc.z/cellSize);
//Check the range and correct it if necessary
if (XCell > Rows) XCell = Rows;
if (YCell > Cols) YCell = Cols;
if (ZCell > Stacks) ZCell = Stacks;
if (XCell < 1) XCell = 0;
if (YCell < 1) YCell = 0;
if (ZCell < 1) ZCell = 0;
//Enumerate the corresponding cell
cellNum = (XCell + (YCell*Rows) + (ZCell*Cols*Rows));
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
////// FUNCTIONS ------------------------------------------------------------------//////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
// RUN:calls what the flocker will do /////////////////////////////////////////////
void run() {
checkPos(); // Check the distance to floxkers arround
display(); // Renders the shape of the Voxel depending on the agents arround
}
////////////////////////////////////////////////////////////////////////////////////
// CHECKPOS: look at the summed distance to the agents in range//////////////////// Stage 3 behaviour: Feed
public void checkPos() {
//Declare a number to store the distance to all agents arround, whithin a certain radius
FtotalDist = 0;
// Roll over the cell and check the Flockers within
for (int j = 0; j < FlockersCollection[cellNum].size(); j++) {
Flocker F = (Flocker) FlockersCollection[cellNum].get(j);
//Find out if the flocker is feeding
if (F.Fenergy < FmaxEnergy*.5) { // Feed the Flockers that are actually starving
// Get the distance to that Flocker
float tempDist = Vloc.dist(F.Floc);
// Get the speed the Flocker is traveling
PVector Fspeed = F.Fvel.get();
//Evaluate if the F agent is moving within the defined radius arround the Voxel.
//If so, add the distance between them to the cummulative distance storing number.
if (tempDist < VoxelSize*1.5/*this is the radius to consider*/ && Fspeed.mag() > 0.2) {
FtotalDist = FtotalDist+(2*tempDist);
}
}
//If the cummulative distance gets over a range, just cap it
if (FtotalDist>200) FtotalDist = 200;
/* Finally, if there are agents flying arround within range, increase opacity.
Otherwise, increase the food count until it reaches zero */
if (FtotalDist > 0) VfoodConsumption += (FtotalDist);
else /*Here comes the damper*/ VfoodConsumption -=replenishRate; //Stage 3 behaviour: Replenish
if (VfoodConsumption < 0) VfoodConsumption = 0;
if (VfoodConsumption > 200) VfoodConsumption = 200;
totalFood = totalFood + VfoodConsumption;
}
}
////////////////////////////////////////////////////////////////////////////////////
// DISPLAY: Renders the object ////////////////////////////////////////////////////
void display() {
//If the opacity is bigger than zero, then draw. This is to optimise the computation timing.
if (VfoodConsumption > 0) {
//Calculate the colour to use accroding to the global checkPos result
fill(13, 132, 255, VfoodConsumption/2);
noStroke();
//Move the drawing matrix to the Voxel's position
pushMatrix();
translate(Vloc.x, Vloc.y, Vloc.z);
//Draw a box of the designed size
box(VoxelSize);
//Return the drawing matrix
popMatrix();
}
}
}
////////////////////////////////////////////////////////////////////////////
/// KEYS AND BUTTONS //////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void keyPressed() {
float increment = 0.2;
if (key == 'a') {
noiseScale = noiseScale*(1+increment/10);
}
if (key == 'z') {
noiseScale = noiseScale/(1+increment/10);
}
if (key == 's') {
align = align+increment;
}
if (key == 'x') {
align = align-increment;
}
if (key == 'd') {
avoid = avoid+increment;
}
if (key == 'c') {
avoid = avoid-increment;
}
if (key == 'f') {
cohesion = cohesion+increment;
}
if (key == 'v') {
cohesion = cohesion-increment;
}
if (key == 'g') {
multiplier = multiplier+increment;
}
if (key == 'b') {
multiplier = multiplier-increment;
}
if (key == 'h') {
maxAge = maxAge+10;
}
if (key == 'n') {
maxAge = maxAge-10;
}
if (key == 'j') {
maxFlockers = maxFlockers+5;
}
if (key == 'm') {
maxFlockers = maxFlockers-5;
}
if (key == 'W') {
replenishRate = replenishRate+0.02; replenishRate = ((replenishRate*1000))/1000;
}
if (key == 'Q') {
replenishRate = replenishRate-0.02; replenishRate = (floor(replenishRate*1000))/1000;
}
}
////////////////////////////////////////////////////////////////////////////
// MOUSE SWITCH ///////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Switch states with the mouse button to display mesages or not ////////
void mouseReleased() {
if (mouseButton == RIGHT) {
MouseSwitch = !MouseSwitch;
}
}
////////////////////////////////////////////////////////////////////////////
// MESSAGES ///////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Lets put a text message with the number of agents on screen at once //
void Message() {
if (MouseSwitch == true) {
int TextUpperBound = 160;
cam.beginHUD();
noStroke();
fill(159, 196, 232, 20);
rect( width-231, height-TextUpperBound+10, 215, TextUpperBound-30);
cam.endHUD();
fill(255, 90);
textMode(SCREEN);
textAlign(RIGHT, BOTTOM);
textSize(15);
text("a-z | Noise scale: " + noiseScale, width-20, height-TextUpperBound+135);
text("s-x | Align: " + align, width-20, height-TextUpperBound+120);
text("d-c | Avoid: " + avoid, width-20, height-TextUpperBound+105);
text("f-v | Cohesion: " + cohesion, width-20, height-TextUpperBound+90);
text("g-b | Multiplier: " + multiplier, width-20, height-TextUpperBound+75);
text("h-n | maxAge:" + maxAge, width-20, height-TextUpperBound+60);
text("j-m | Population:" + maxFlockers + "(" + flockerCounter + ")", width-20, height-TextUpperBound+45);
text("q-w | replenishRate:" + replenishRate, width-20, height-TextUpperBound+30);
text("KEYS ", width-20, height-TextUpperBound+15);
text("totalFood:" + totalFood, width-20, height-TextUpperBound);
}
//text("----Size" + Size, width-20, height-85);
// text("Cells | Population: "+Cells + "|"+FlockerPop.size(), width-20, height-70);
}