From d685d518b3f271f584e96324fa00c995a342591d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Kn=C3=B6rig?= Date: Thu, 5 Mar 2015 16:52:54 +0100 Subject: [PATCH] finished --- .classpath | 12 ++ .project | 17 +++ src/Birds.java | 259 ++++++++++++++++++++++++++++++++++++ src/FlockingApp.java | 144 ++++++++++++++++++++ src/Particle.java | 138 +++++++++++++++++++ src/ParticleController.java | 252 +++++++++++++++++++++++++++++++++++ src/Predator.java | 141 ++++++++++++++++++++ 7 files changed, 963 insertions(+) create mode 100755 .classpath create mode 100755 .project create mode 100755 src/Birds.java create mode 100755 src/FlockingApp.java create mode 100755 src/Particle.java create mode 100755 src/ParticleController.java create mode 100755 src/Predator.java 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++; + } +}