diff --git a/.classpath b/.classpath
new file mode 100755
index 0000000..5c2d6d5
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.project b/.project
new file mode 100755
index 0000000..f9f4a7c
--- /dev/null
+++ b/.project
@@ -0,0 +1,17 @@
+
+
+ birds
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/src/Birds.java b/src/Birds.java
new file mode 100755
index 0000000..16e0630
--- /dev/null
+++ b/src/Birds.java
@@ -0,0 +1,259 @@
+import processing.core.PApplet;
+import processing.opengl.*;
+import controlP5.*;
+
+
+public class Birds extends PApplet {
+ ControlP5 controlP5;
+
+ // Flock array
+ int birdCount = 200;
+ Bird[]birds;
+ float[]x, y, z;
+ float[]rx, ry, rz;
+ float birdSizeMin, birdSizeMax;
+ float[]spd, rot;
+ float spdMin, spdMax;
+ float rotMin, rotMax;
+
+ boolean paused = false;
+
+ public void setup() {
+ size(640, 640, OPENGL);
+ noStroke();
+ controlP5 = new ControlP5(this);
+ // name, min, max, default, x, y, width, height
+ controlP5.addSlider("birds",0,1000,200, 10,10,200,10).setId(0);
+ // name, min, max, defMin, defMax, x, y, width, height
+ controlP5.addRange("birdSize",0f,50f,5f,30f, 10,30,200,10).setId(1);
+ controlP5.addRange("flap speed",0f,5f,0.1f,3.75f, 10,50,200,10).setId(2);
+ controlP5.addRange("rotation",0f,0.2f,0.025f,0.15f, 10,70,200,10).setId(3);
+ recreate();
+ }
+
+ void recreate() {
+ createBirds();
+ feedBirds();
+ }
+
+ void createBirds() {
+ birds = new Bird[birdCount];
+ for (int i = 0; i < birdCount; i++){
+ float birdSize = random(birdSizeMin, birdSizeMax);
+ birds[i] = new Bird(
+ random(-300, 300), random(-300, 300), random(-500, -2500), // pos
+ birdSize, birdSize); // size
+ }
+ }
+
+ void feedBirds() {
+ x = new float[birdCount];
+ y = new float[birdCount];
+ z = new float[birdCount];
+ rx = new float[birdCount];
+ ry = new float[birdCount];
+ rz = new float[birdCount];
+ spd = new float[birdCount];
+ rot = new float[birdCount];
+ for (int i = 0; i < birdCount; i++){
+ x[i] = random(20, 340);
+ y[i] = random(30, 350);
+ z[i] = random(1000, 4800);
+ rx[i] = random(-160, 160);
+ ry[i] = random(-55, 55);
+ rz[i] = random(-20, 20);
+ spd[i] = random(spdMin, spdMax);
+ rot[i] = random(rotMin, rotMax);
+ }
+ }
+
+
+ public void draw() {
+ if (!paused) {
+ background(0);
+ lights();
+ for (int i = 0; i < birdCount; i++){
+ birds[i].setFlight(x[i], y[i], z[i], rx[i], ry[i], rz[i]);
+ birds[i].setWingSpeed(spd[i]);
+ birds[i].setRotSpeed(rot[i]);
+ birds[i].fly();
+ }
+ }
+ }
+
+ public void controlEvent(ControlEvent e) {
+ switch(e.controller().id()) {
+ case(0):
+ birdCount = (int)(e.controller().value());
+ recreate();
+ break;
+ case(1):
+ birdSizeMin = e.controller().arrayValue()[0];
+ birdSizeMax = e.controller().arrayValue()[1];
+ recreate();
+ break;
+ case(2):
+ spdMin = e.controller().arrayValue()[0];
+ spdMax = e.controller().arrayValue()[1];
+ feedBirds();
+ break;
+ case(3):
+ rotMin = e.controller().arrayValue()[0];
+ rotMax = e.controller().arrayValue()[1];
+ feedBirds();
+ break;
+ }
+ }
+
+ void togglePause() {
+ paused = !paused;
+ }
+
+ public void keyPressed() {
+ switch (key) {
+ case(' '):
+ togglePause();
+ break;
+ }
+ }
+
+
+ ////////////// B I R D ////////////////////////
+
+
+ public class Bird {
+ // Properties
+ float offsetX, offsetY, offsetZ;
+ float w, h;
+ final int leftWingTopFill = color(236, 101, 83);
+ final int leftWingBottomFill = color(249, 208, 83);
+ final int rightWingTopFill = color(132, 26, 55);
+ final int rightWingBottomFill = color(245, 171, 90);
+
+ float flapSpeed; // wing flapping speed
+ float rotSpeed; // rotation speed
+ float radiusX, radiusY, radiusZ; // ??
+ float rotX, rotY, rotZ; // bird rotation
+ float ang, ang2, ang3, ang4; // motion progress
+
+ // Constructors
+ Bird() {
+ this(0, 0, 0, 60, 80);
+ }
+
+ Bird(float offsetX, float offsetY, float offsetZ, float w, float h) {
+ this.offsetX = offsetX;
+ this.offsetY = offsetY;
+ this.offsetZ = offsetZ;
+ this.h = h;
+ this.w = w;
+ }
+
+ void setFlight(float radiusX, float radiusY, float radiusZ, float rotX,
+ float rotY, float rotZ) {
+ this.radiusX = radiusX;
+ this.radiusY = radiusY;
+ this.radiusZ = radiusZ;
+
+ this.rotX = rotX;
+ this.rotY = rotY;
+ this.rotZ = rotZ;
+ }
+
+ void setWingSpeed(float flapSpeed) {
+ this.flapSpeed = flapSpeed;
+ }
+
+ void setRotSpeed(float rotSpeed) {
+ this.rotSpeed = rotSpeed;
+ }
+
+ void fly() {
+ pushMatrix();
+ float px, py, pz;
+
+ // Flight
+ px = sin(radians(ang3)) * radiusX;
+ py = cos(radians(ang3)) * radiusY;
+ pz = sin(radians(ang4)) * radiusZ;
+
+ translate(width / 2 + offsetX + px, height / 2 + offsetY + py, -700
+ + offsetZ + pz);
+
+ rotateX(sin(radians(ang2)) * rotX);
+ rotateY(sin(radians(ang2)) * rotY);
+ rotateZ(sin(radians(ang2)) * rotZ);
+
+ // Body
+ // fill(bodyFill);
+ // box(w/5, h, w/5);
+
+ // Left wing
+ pushMatrix();
+ rotateY(sin(radians(ang)) * 20);
+ // top
+ fill(leftWingTopFill);
+ beginShape();
+ vertex(0, -h / 3);
+ vertex(0, -h);
+ vertex(w / 2, -h * 2 / 3f);
+ vertex(w / 2, 0);
+ endShape(CLOSE);
+ // bottom
+ pushMatrix();
+ translate(0, 0, -0.01f);
+ fill(leftWingBottomFill);
+ beginShape();
+ vertex(0, -h / 3);
+ vertex(0, -h);
+ vertex(w / 2, -h * 2 / 3f);
+ vertex(w / 2, 0);
+ endShape(CLOSE);
+ popMatrix();
+
+ popMatrix();
+
+ // Right wing
+ pushMatrix();
+ rotateY(sin(radians(ang)) * -20);
+ // top
+ fill(rightWingTopFill);
+ beginShape();
+ vertex(-w / 2, 0);
+ vertex(-w / 2, -h * 2 / 3f);
+ vertex(0, -h);
+ vertex(0, -h / 3);
+ endShape(CLOSE);
+ // bottom
+ pushMatrix();
+ translate(0, 0, -0.01f);
+ fill(rightWingBottomFill);
+ beginShape();
+ vertex(-w / 2, 0);
+ vertex(-w / 2, -h * 2 / 3f);
+ vertex(0, -h);
+ vertex(0, -h / 3);
+ endShape(CLOSE);
+ popMatrix();
+
+ popMatrix();
+
+ // Wing flap
+ ang += flapSpeed;
+ if (ang > 3) {
+ flapSpeed *= -1;
+ }
+ if (ang < -3) {
+ flapSpeed *= -1;
+ }
+ ang2 += rotSpeed; // bird orientation
+ ang3 += 1.25; // x-/y-movement within the flock
+ ang4 += 0.55; // flock z-axis
+ popMatrix();
+ }
+
+ }
+
+}
+
+
diff --git a/src/FlockingApp.java b/src/FlockingApp.java
new file mode 100755
index 0000000..13efa5f
--- /dev/null
+++ b/src/FlockingApp.java
@@ -0,0 +1,144 @@
+import processing.core.PApplet;
+import processing.core.PMatrix3D;
+import toxi.geom.Quaternion;
+import toxi.geom.Vec3D;
+import controlP5.ControlEvent;
+import controlP5.ControlP5;
+import controlP5.Toggle;
+
+public class FlockingApp extends PApplet {
+ private static final int NUM_INITIAL_PARTICLES = 2000;
+ private static final int NUM_INITIAL_PREDATORS = 2;
+ private static final int NUM_PARTICLES_TO_SPAWN = 1000;
+
+ // PARAMS
+ ControlP5 controlP5;
+
+ // CAMERA
+ PMatrix3D currCameraMatrix;
+ Quaternion mSceneRotation;
+ Vec3D mEye, mCenter, mUp;
+ float mCameraDistance;
+
+ ParticleController mParticleController;
+ float mZoneRadius;
+ float mLowerThresh, mHigherThresh;
+ float mAttractStrength, mRepelStrength, mOrientStrength;
+
+ boolean mCentralGravity;
+ boolean mFlatten;
+ boolean mSaveFrames;
+ boolean mIsRenderingPrint;
+
+ boolean paused = false;
+
+ public void setup() {
+ size(800, 600, OPENGL);
+ frameRate(30);
+ sphereDetail(5);
+ colorMode(RGB, 1.0f);
+
+ mParticleController = new ParticleController(this);
+
+ mCenter = new Vec3D(width * 0.5f, height * 0.5f, 0.0f);
+ mCentralGravity = true;
+ mFlatten = false;
+ mSaveFrames = false;
+ mIsRenderingPrint = false;
+ mZoneRadius = 80.0f;
+ mLowerThresh = 0.5f;
+ mHigherThresh = 0.8f;
+ mAttractStrength = 0.004f;
+ mRepelStrength = 0.01f;
+ mOrientStrength = 0.01f;
+
+ // SETUP CAMERA
+ mCameraDistance = 750.0f;
+ mEye = new Vec3D( 0.0f, 0.0f, mCameraDistance );
+ mCenter = new Vec3D(Vec3D.ZERO);
+ mUp = new Vec3D(Vec3D.Y_AXIS);
+ perspective(75.0f, width/height, 5.0f, 5000.0f);
+ mSceneRotation = new Quaternion();
+
+ // SETUP PARAMS
+ // TODO: fix camera for params gui
+// controlP5 = new ControlP5(this);
+// controlP5.addToggle("Center Gravity",mCentralGravity,10,0,30,10).setId(2);
+// controlP5.addToggle("Flatten",mFlatten,100,0,30,10).setId(3);
+// controlP5.addSlider("Zone Radius",10.0f,100.0f,mZoneRadius, 10,30,200,10).setId(4);
+// controlP5.addSlider("Lower Thresh",0.025f,1.0f,mLowerThresh, 10,50,200,10).setId(5);
+// controlP5.addSlider("Higher Thresh",0.025f,1.0f,mHigherThresh, 10,70,200,10).setId(6);
+// controlP5.addSlider("Attract Strength",0.001f,0.1f,mAttractStrength, 10,100,200,10).setId(7);
+// controlP5.addSlider("Repel Strength",0.001f,0.1f,mRepelStrength, 10,120,200,10).setId(8);
+// controlP5.addSlider("Orient Strength",0.001f,0.1f,mOrientStrength, 10,140,200,10).setId(9);
+
+ // CREATE PARTICLE CONTROLLER
+ mParticleController.addParticles(NUM_INITIAL_PARTICLES);
+ mParticleController.addPredators(NUM_INITIAL_PREDATORS);
+ }
+
+ void update() {
+ if (mLowerThresh > mHigherThresh) {
+ mHigherThresh = mLowerThresh;
+ }
+ mParticleController.applyForceToPredators(mZoneRadius, 0.4f, 0.7f);
+ mParticleController.applyForceToParticles(mZoneRadius, mLowerThresh,
+ mHigherThresh, mAttractStrength, mRepelStrength, mOrientStrength);
+ if (mCentralGravity) {
+ mParticleController.pullToCenter(mCenter);
+ }
+ mParticleController.update(mFlatten);
+
+ mEye = new Vec3D( 0.0f, 0.0f, mCameraDistance );
+ camera(mEye.x, mEye.y, mEye.z,
+ mCenter.x, mCenter.y, mCenter.z,
+ mUp.x, mUp.y, mUp.z);
+ rotate(mSceneRotation.w, mSceneRotation.x, mSceneRotation.y, mSceneRotation.z);
+ }
+
+ public void draw() {
+ update();
+ background(0);
+ lights();
+ fill(1.0f);
+ mParticleController.draw();
+ }
+
+ public void keyPressed() {
+ if (key == 'p') {
+ mParticleController.addParticles(NUM_PARTICLES_TO_SPAWN);
+ } else if (key == ' ') {
+ saveFrame();
+ }
+ }
+
+ public void controlEvent(ControlEvent e) {
+ switch(e.controller().id()) {
+ case(2):
+ mCentralGravity = ((Toggle)e.controller()).getState();
+ break;
+ case(3):
+ mFlatten = ((Toggle)e.controller()).getState();
+ break;
+ case(4):
+ mZoneRadius = e.controller().value();
+ break;
+ case(5):
+ mLowerThresh = e.controller().value();
+ break;
+ case(6):
+ mHigherThresh = e.controller().value();
+ break;
+ case(7):
+ mAttractStrength = e.controller().value();
+ break;
+ case(8):
+ mRepelStrength = e.controller().value();
+ break;
+ case(9):
+ mOrientStrength = e.controller().value();
+ break;
+ }
+ }
+
+}
diff --git a/src/Particle.java b/src/Particle.java
new file mode 100755
index 0000000..6d12be2
--- /dev/null
+++ b/src/Particle.java
@@ -0,0 +1,138 @@
+import java.awt.Color;
+
+import processing.core.PApplet;
+import toxi.geom.Vec3D;
+
+public class Particle {
+ PApplet p;
+
+ Vec3D mPos;
+ Vec3D mTailPos;
+ Vec3D mVel;
+ Vec3D mVelNormal;
+ Vec3D mAcc;
+
+ Vec3D mNeighborPos;
+ int mNumNeighbors;
+
+ Color mColor;
+
+ float mDecay;
+ float mRadius;
+ float mLength;
+ float mMaxSpeed, mMaxSpeedSqrd;
+ float mMinSpeed, mMinSpeedSqrd;
+ float mFear;
+ float mCrowdFactor;
+
+ boolean mIsDead;
+ boolean mFollowed;
+
+ Particle(PApplet p) {
+ this.p = p;
+ }
+
+ Particle(Vec3D pos, Vec3D vel, boolean followed, PApplet p) {
+ this.p = p;
+
+ mPos = pos;
+ mTailPos = pos;
+ mVel = vel;
+ mVelNormal = new Vec3D(Vec3D.Y_AXIS);
+ mAcc = new Vec3D(Vec3D.ZERO);
+
+ mNeighborPos = new Vec3D(Vec3D.ZERO);
+ mNumNeighbors = 0;
+ mMaxSpeed = p.random(2.5f, 4.0f);
+ mMaxSpeedSqrd = mMaxSpeed * mMaxSpeed;
+ mMinSpeed = p.random(1.0f, 1.5f);
+ mMinSpeedSqrd = mMinSpeed * mMinSpeed;
+
+ mColor = new Color(1.0f, 1.0f, 1.0f, 1.0f);
+
+ mDecay = 0.99f;
+ mRadius = 1.0f;
+ mLength = 5.0f;
+ mFear = 1.0f;
+ mCrowdFactor = 1.0f;
+
+ mIsDead = false;
+ mFollowed = followed;
+ }
+
+ void pullToCenter(final Vec3D center) {
+ Vec3D dirToCenter = mPos.sub(center);
+ float distToCenter = dirToCenter.magnitude();
+ float distThresh = 200.0f;
+
+ if (distToCenter > distThresh) {
+ dirToCenter.normalize();
+ float pullStrength = 0.00025f;
+ mVel.subSelf(dirToCenter.scale(
+ ((distToCenter - distThresh) * pullStrength)));
+ }
+ }
+
+ void update(boolean flatten) {
+ mCrowdFactor -= (mCrowdFactor - (1.0f - mNumNeighbors * 0.02f)) * 0.1f;
+ mCrowdFactor = PApplet.constrain(mCrowdFactor, 0.5f, 1.0f);
+
+ mFear -= (mFear - 0.0f) * 0.2f;
+
+ if (flatten)
+ mAcc.z = 0.0f;
+
+ mVel.addSelf(mAcc);
+ mVelNormal = mVel.getNormalized();
+
+ limitSpeed();
+
+ mPos.addSelf(mVel);
+ if (flatten)
+ mPos.z = 0.0f;
+
+ mTailPos = mPos.sub(mVelNormal.scale(mLength));
+ mVel.scaleSelf(mDecay);
+
+ float c = PApplet.min(mNumNeighbors / 50.0f, 1.0f);
+ mColor = Color.getHSBColor(1.0f - c, c, c * 0.5f + 0.5f);
+
+ mAcc = new Vec3D(Vec3D.ZERO);
+ mNeighborPos = new Vec3D(Vec3D.ZERO);
+ mNumNeighbors = 0;
+ }
+
+ void limitSpeed() {
+ float maxSpeed = mMaxSpeed + mCrowdFactor;
+ float maxSpeedSqrd = maxSpeed * maxSpeed;
+
+ float vLengthSqrd = mVel.magSquared();
+ if (vLengthSqrd > maxSpeedSqrd) {
+ mVel = mVelNormal.scale(maxSpeed);
+
+ } else if (vLengthSqrd < mMinSpeedSqrd) {
+ mVel = mVelNormal.scale(mMinSpeed);
+ }
+ mVel.scaleSelf(1.0f + mFear);
+ }
+
+ void draw() {
+ p.stroke(mColor.getRGB());
+ p.strokeWeight(2);
+ Vec3D start = mPos.sub(mVelNormal.scale(mLength));
+ Vec3D end = mPos.sub(mVelNormal.scale(mLength * 0.75f));
+ p.line(start.x, start.y, start.z, end.x, end.y, end.z);
+ }
+
+ void drawTail() {
+ /*
+ * TODO: p.fill(mColor.getRGB()); glVertex3fv( mPos ); p.fill(mColor.getRGB());
+ * glVertex3fv( mTailPos );
+ */
+ }
+
+ void addNeighborPos(Vec3D pos) {
+ mNeighborPos.addSelf(pos);
+ mNumNeighbors++;
+ }
+}
diff --git a/src/ParticleController.java b/src/ParticleController.java
new file mode 100755
index 0000000..3ecbd20
--- /dev/null
+++ b/src/ParticleController.java
@@ -0,0 +1,252 @@
+import java.util.ArrayList;
+
+import processing.core.PApplet;
+import processing.core.PConstants;
+import toxi.geom.Vec3D;
+import toxi.math.noise.PerlinNoise;
+
+public class ParticleController {
+ PApplet p;
+
+ PerlinNoise mPerlin;
+
+ ArrayList mParticles;
+ ArrayList mPredators;
+ Vec3D mParticleCentroid;
+ int mNumParticles;
+
+ ParticleController(PApplet p) {
+ this.p = p;
+ mPerlin = new PerlinNoise();
+ mParticles = new ArrayList();
+ mPredators = new ArrayList();
+ }
+
+ void applyForceToParticles(float zoneRadius, float lowerThresh,
+ float higherThresh, float attractStrength, float repelStrength,
+ float alignStrength) {
+ float twoPI = PConstants.PI * 2.0f;
+ mParticleCentroid = new Vec3D(Vec3D.ZERO);
+ mNumParticles = mParticles.size();
+
+ for (int i = 0; i < mParticles.size(); i++) {
+ Particle p1 = mParticles.get(i);
+
+ for (int j = i+1; j < mParticles.size(); j++) {
+ Particle p2 = mParticles.get(j);
+
+ Vec3D dir = p1.mPos.sub(p2.mPos);
+ float distSqrd = dir.magSquared();
+ float zoneRadiusSqrd = zoneRadius * p1.mCrowdFactor
+ * zoneRadius * p2.mCrowdFactor;
+
+ if (distSqrd < zoneRadiusSqrd) { // Neighbor is in the zone
+ float per = distSqrd / zoneRadiusSqrd;
+ p1.addNeighborPos(p2.mPos);
+ p2.addNeighborPos(p1.mPos);
+
+ // Separation
+ if (per < lowerThresh) {
+ float F = (lowerThresh / per - 1.0f) * repelStrength;
+ dir.normalize();
+ dir.scaleSelf(F);
+
+ p1.mAcc.addSelf(dir);
+ p2.mAcc.subSelf(dir);
+ }
+ // Alignment
+ else if (per < higherThresh) {
+ float threshDelta = higherThresh - lowerThresh;
+ float adjPer = (per - lowerThresh) / threshDelta;
+ float F = (1.0f - (PApplet.cos(adjPer * twoPI) * -0.5f + 0.5f))
+ * alignStrength;
+
+ p1.mAcc.addSelf(p2.mVelNormal.scale(F));
+ p2.mAcc.addSelf(p1.mVelNormal.scale(F));
+
+ }
+ // Cohesion (prep)
+ else {
+ float threshDelta = 1.0f - higherThresh;
+ float adjPer = (per - higherThresh) / threshDelta;
+ float F = (1.0f - (PApplet.cos(adjPer * twoPI) * -0.5f + 0.5f))
+ * attractStrength;
+
+ dir.normalize();
+ dir.scaleSelf(F);
+
+ p1.mAcc.subSelf(dir);
+ p2.mAcc.addSelf(dir);
+ }
+
+ }
+ }
+
+ mParticleCentroid.addSelf(p1.mPos);
+ /*
+ * if( p1.mNumNeighbors > 0 ){ // Cohesion Vec3D neighborAveragePos
+ * = ( p1.mNeighborPos/(float)p1.mNumNeighbors ); p1.mAcc += (
+ * neighborAveragePos - p1.mPos ) * attractStrength; }
+ */
+
+ // ADD PERLIN NOISE INFLUENCE
+ float scale = 0.002f;
+ float multi = 0.01f;
+ // TODO: check if this really creates a good noise
+ float perlinX = mPerlin.noise(p1.mPos.x * scale) * multi;
+ float perlinY = mPerlin.noise(p1.mPos.y * scale) * multi;
+ float perlinZ = mPerlin.noise(p1.mPos.z * scale) * multi;
+ p1.mAcc.addSelf(new Vec3D(perlinX, perlinY, perlinZ));
+
+
+ // CHECK WHETHER THERE IS ANY PARTICLE/PREDATOR INTERACTION
+ float eatDistSqrd = 50.0f;
+ float predatorZoneRadiusSqrd = zoneRadius * zoneRadius * 5.0f;
+ for (Predator predator : mPredators) {
+
+ Vec3D dir = p1.mPos.sub(predator.mPos[0]);
+ float distSqrd = dir.magSquared();
+
+ if (distSqrd < predatorZoneRadiusSqrd) {
+ if (distSqrd > eatDistSqrd) {
+ float F = (predatorZoneRadiusSqrd / distSqrd - 1.0f) * 0.1f;
+ p1.mFear += F * 0.1f;
+ dir = dir.getNormalized().scale(F);
+ p1.mAcc.addSelf(dir);
+ if (predator.mIsHungry)
+ predator.mAcc.addSelf(dir.scale(0.04f * predator.mHunger));
+ } else {
+ p1.mIsDead = true;
+ predator.mHunger = 0.0f;
+ predator.mIsHungry = false;
+ }
+ }
+ }
+
+
+ }
+ mParticleCentroid.scaleSelf(1 / (float) mNumParticles);
+ }
+
+ void applyForceToPredators(float zoneRadius, float lowerThresh,
+ float higherThresh) {
+ float twoPI = PConstants.PI * 2.0f;
+
+ for (int i = 0; i < mPredators.size(); i++) {
+ Predator P1 = mPredators.get(i);
+
+ for (int j = i+1; j < mPredators.size(); j++) {
+ Predator P2 = mPredators.get(j);
+
+ Vec3D dir = P1.mPos[0].sub(P2.mPos[0]);
+ float distSqrd = dir.magSquared();
+ float zoneRadiusSqrd = zoneRadius * zoneRadius * 4.0f;
+
+ if (distSqrd < zoneRadiusSqrd) { // Neighbor is in the zone
+ float per = distSqrd / zoneRadiusSqrd;
+ if (per < lowerThresh) { // Separation
+ float F = (lowerThresh / per - 1.0f) * 0.01f;
+ dir.normalize();
+ dir.scale(F);
+
+ P1.mAcc.addSelf(dir);
+ P2.mAcc.subSelf(dir);
+ } else if (per < higherThresh) { // Alignment
+ float threshDelta = higherThresh - lowerThresh;
+ float adjPer = (per - lowerThresh) / threshDelta;
+ float F = (1.0f - PApplet.cos(adjPer * twoPI) * -0.5f + 0.5f) * 0.3f;
+
+ P1.mAcc.addSelf(P2.mVelNormal.scale(F));
+ P2.mAcc.addSelf(P1.mVelNormal.scale(F));
+
+ } else { // Cohesion
+ float threshDelta = 1.0f - higherThresh;
+ float adjPer = (per - higherThresh) / threshDelta;
+ float F = (1.0f - (PApplet.cos(adjPer * twoPI) * -0.5f + 0.5f)) * 0.1f;
+
+ dir.normalize();
+ dir.scaleSelf(F);
+
+ P1.mAcc.subSelf(dir);
+ P2.mAcc.addSelf(dir);
+ }
+ }
+ }
+
+ }
+ }
+
+ void pullToCenter(final Vec3D center) {
+ for (Particle p1 : mParticles) {
+ p1.pullToCenter(center);
+ }
+
+ for (Predator p1 : mPredators) {
+ p1.pullToCenter(center);
+ }
+ }
+
+ void update(boolean flatten) {
+ ArrayList removeParticles = new ArrayList();
+ for (Particle p1 : mParticles) {
+ if (p1.mIsDead) {
+ removeParticles.add(p1);
+ } else {
+ p1.update(flatten);
+ }
+ }
+ for (Particle p1 : removeParticles) {
+ mParticles.remove(p1);
+ }
+
+ for (Predator p1 : mPredators) {
+ p1.update(flatten);
+ }
+ }
+
+ void draw() {
+ // DRAW PREDATOR ARROWS
+ for (Predator p1 : mPredators) {
+ float hungerColor = 1.0f - p1.mHunger;
+ p.stroke(1.0f, hungerColor, hungerColor, 1.0f);
+ p1.draw();
+ }
+
+ // DRAW PARTICLE ARROWS
+ for (Particle p1 : mParticles) {
+ p1.draw();
+ }
+ }
+
+ void addPredators(int amt) {
+ for (int i = 0; i < amt; i++) {
+ Vec3D pos = Vec3D.randomVector().scale(p.random(500.0f, 750.0f));
+ Vec3D vel = Vec3D.randomVector();
+ mPredators.add(new Predator(pos, vel, p));
+ }
+ }
+
+ void addParticles(int amt) {
+ for (int i = 0; i < amt; i++) {
+ Vec3D pos = Vec3D.randomVector().scale(p.random(100.0f, 200.0f));
+ Vec3D vel = Vec3D.randomVector();
+
+ boolean followed = false;
+ if (mParticles.size() == 0)
+ followed = true;
+
+ mParticles.add(new Particle(pos, vel, followed, p));
+ }
+ }
+
+ void removeParticles(int amt) {
+ for (int i = 0; i < amt; i++) {
+ mParticles.remove(mParticles.size() - 1);
+ }
+ }
+
+ Vec3D getPos() {
+ return mParticles.iterator().next().mPos;
+ }
+
+}
diff --git a/src/Predator.java b/src/Predator.java
new file mode 100755
index 0000000..bad20a8
--- /dev/null
+++ b/src/Predator.java
@@ -0,0 +1,141 @@
+import java.awt.Color;
+
+import processing.core.PApplet;
+import toxi.geom.Vec3D;
+
+public class Predator {
+ PApplet p;
+
+ int mLen;
+ float mInvLen;
+ Vec3D[] mPos;
+
+ Vec3D mVel;
+ Vec3D mVelNormal;
+ Vec3D mAcc;
+
+ Color mColor;
+
+ Vec3D mNeighborPos;
+ int mNumNeighbors;
+
+ float mDecay;
+ float mRadius;
+ float mLength;
+ float mMaxSpeed, mMaxSpeedSqrd;
+ float mMinSpeed, mMinSpeedSqrd;
+ float mHunger;
+
+ boolean mIsHungry;
+ boolean mIsDead;
+
+ Predator(Vec3D pos, Vec3D vel, PApplet p) {
+ this.p = p;
+
+ mLen = 5;
+ mInvLen = 1.0f / (float) mLen;
+
+ mPos = new Vec3D[5];
+ for (int i = 0; i < mLen; ++i) {
+ mPos[i] = pos;
+ }
+
+ mVel = vel;
+ mVelNormal = new Vec3D(Vec3D.Y_AXIS);
+ mAcc = new Vec3D(Vec3D.ZERO);
+
+ mNeighborPos = new Vec3D(Vec3D.ZERO);
+ mNumNeighbors = 0;
+ mMaxSpeed = p.random(4.0f, 4.5f);
+ mMaxSpeedSqrd = mMaxSpeed * mMaxSpeed;
+ mMinSpeed = p.random(1.0f, 1.5f);
+ mMinSpeedSqrd = mMinSpeed * mMinSpeed;
+
+ mColor = new Color(1.0f, 0.0f, 0.0f, 1.0f);
+
+ mDecay = 0.99f;
+ mRadius = 2.0f;
+ mLength = 25.0f;
+ mHunger = 1.0f;
+
+ mIsDead = false;
+ mIsHungry = true;
+ }
+
+ void pullToCenter(final Vec3D center) {
+ Vec3D dirToCenter = mPos[0].sub(center);
+ float distToCenter = dirToCenter.magnitude();
+ float maxDistance = 600.0f;
+
+ if (distToCenter > maxDistance) {
+ float pullStrength = 0.0001f;
+ mVel.subSelf(dirToCenter.getNormalized().scale(
+ ((distToCenter - maxDistance) * pullStrength)));
+ }
+ }
+
+ void update(boolean flatten) {
+ mVel.addSelf(mAcc);
+
+ if (flatten)
+ mAcc.z = 0.0f;
+ mVel.addSelf(mAcc);
+ mVelNormal = mVel.getNormalized();
+
+ limitSpeed();
+
+ for (int i = mLen - 1; i > 0; i--) {
+ mPos[i] = mPos[i - 1];
+ }
+ mPos[0].addSelf(mVel);
+
+ if (flatten)
+ mPos[0].z = 0.0f;
+
+ mVel.scaleSelf(mDecay);
+
+ mAcc = new Vec3D(Vec3D.ZERO);
+ mNeighborPos = new Vec3D(Vec3D.ZERO);
+ mNumNeighbors = 0;
+
+ mHunger += 0.001f;
+ mHunger = PApplet.constrain(mHunger, 0.0f, 1.0f);
+
+ if (mHunger > 0.5f)
+ mIsHungry = true;
+ }
+
+ void limitSpeed() {
+ float maxSpeed = mMaxSpeed + mHunger * 3.0f;
+ float maxSpeedSqrd = maxSpeed * maxSpeed;
+ float vLengthSqrd = mVel.magSquared();
+ if (vLengthSqrd > maxSpeedSqrd) {
+ mVel = mVelNormal.scale(maxSpeed);
+
+ } else if (vLengthSqrd < mMinSpeedSqrd) {
+ mVel = mVelNormal.scale(mMinSpeed);
+ }
+ }
+
+ void draw() {
+ Vec3D vel = mVelNormal.scale(mLength);
+
+ p.stroke(mColor.getRGB());
+ p.strokeWeight(3.0f + mHunger);
+ Vec3D start = mPos[0].sub(mVel);
+ Vec3D end = mPos[0];
+ p.line(start.x, start.y, start.z, end.x, end.y, end.z);
+ }
+
+ void drawTail() {
+ p.stroke(mColor.getRGB());
+ p.strokeWeight(5);
+ p.line(mPos[0].x, mPos[0].y, mPos[0].z,
+ mPos[1].x, mPos[1].y, mPos[1].z);
+ }
+
+ void addNeighborPos(Vec3D pos) {
+ mNeighborPos.addSelf(pos);
+ mNumNeighbors++;
+ }
+}