static final boolean DEBUG = false;
static final int SIZE = 75;
static final int BLACK = 0;
static final int WHITE = 255;
monster.setInterest(mouseX, mouseY);
void showVector(PVector vector, color lineColor) {
PVector magnified = PVector.mult(vector, 10);
line(0, 0, magnified.x, magnified.y);
float diffAngle(float from, float to) {
while (diff < -PI) diff += TWO_PI;
while (diff > PI) diff -= TWO_PI;
void rotatePVector(PVector pv, float angle) {
float magnitude = pv.mag();
float currentAngle = pv.heading2D();
float newAngle = currentAngle + angle;
pv.set((float) magnitude*cos(newAngle), (float) magnitude*sin(newAngle), 0.0);
color furColor = #000000;
ArrayList hair = new ArrayList();
position = new PVector(random(SIZE, width-SIZE), random(SIZE, height-SIZE));
globalInterest = new PVector(position.x, position.y);
localInterest = new PVector(0, 0);
velocity = new PVector(0, 0);
desire = new PVector(0, 0);
void setInterest(float x, float y) {
localInterest.x = x - position.x;
localInterest.y = y - position.y;
rotatePVector(localInterest, PI-orientation-PI);
float desireSpeed = desire.mag();
float desireTheta = desire.heading2D();
desireSpeed = constrain(desireSpeed, 0, maxSpeed);
float currentTheta = velocity.heading2D();
float thetaDelta = diffAngle(currentTheta, desireTheta);
thetaDelta = constrain(thetaDelta, -maxTurn, maxTurn);
desireTheta = currentTheta + (thetaDelta/2 + (thetaDelta/2 * desireSpeed/maxSpeed));
velocity.set((float) desireSpeed*cos(desireTheta), (float) desireSpeed*sin(desireTheta), 0.0);
orientation = desireTheta - HALF_PI;
position.x = constrain(position.x, radius, width-radius);
position.y = constrain(position.y, radius, height-radius);
void fear(PVector repulsor) {
float distance = PVector.dist(position, repulsor);
if (distance <= repulseReach) {
PVector away = PVector.sub(position, repulsor);
away.mult(pow(1-(distance/repulseReach),2)*repulseStrength);
void seek(PVector attractor) {
float distance = PVector.dist(position, attractor);
if (distance <= attractReach) {
PVector toward = PVector.sub(attractor, position);
toward.mult(pow(1-(distance/attractReach),1)*attractStrength);
ellipse(position.x, position.y, SIZE, SIZE);
translate(position.x,position.y);
renderEye(-SIZE*0.15, SIZE*0.25);
renderEye(SIZE*0.15, SIZE*0.25);
showVector(desire, #0000FF);
showVector(velocity, #00FF00);
ellipse(localInterest.x, localInterest.y, 2, 2);
void renderEye(float x, float y) {
ellipse(0, 0, SIZE*0.15, SIZE*0.18);
PVector towardInterest = new PVector(localInterest.x, localInterest.y);
towardInterest.normalize();
translate(towardInterest.x, towardInterest.y);
float strandLocalX, strandLocalY;
float strandGlobalX, strandGlobalY;
translate(position.x,position.y);
while (arclength < radius * TWO_PI) {
variance = noise(hairNumber * .3);
strandTheta = arclength/radius;
strandLocalX = radius*cos(strandTheta);
strandLocalY = radius*sin(strandTheta);
strandGlobalX = screenX(strandLocalX, strandLocalY);
strandGlobalY = screenY(strandLocalX, strandLocalY);
HairStrand strand = new HairStrand(strandGlobalX, strandGlobalY, strandTheta, variance);
float strandLocalX, strandLocalY;
float strandGlobalX, strandGlobalY;
translate(position.x,position.y);
for (int i=hair.size()-1; i >= 0; i--) {
strand = (HairStrand) hair.get(i);
strandTheta = -arclength/radius;
strandLocalX = radius*cos(strandTheta);
strandLocalY = radius*sin(strandTheta);
strandGlobalX = screenX(strandLocalX, strandLocalY);
strandGlobalY = screenY(strandLocalX, strandLocalY);
strand.moveRoot(strandGlobalX, strandGlobalY, orientation+strandTheta);
for (int i=hair.size()-1; i >= 0; i--) {
strand = (HairStrand) hair.get(i);
HairStrand(float x, float y, float newTheta, float variance) {
root = new PVector(x, y);
length = 5 + (5 * (variance % 5)) + (20 * variance);
if (DEBUG) arc = arc * 10;
tip = new PVector(root.x+(length*cos(theta)), root.y+(length*sin(theta)), 0.0);
float halfLength = length/2;
halfway = new PVector(root.x+(halfLength*cos(theta)), root.y+(halfLength*sin(theta)), 0.0);
tipMaxSpeed = sq(length)/500;
tipVelocity = new PVector(0,0);
tipDesire = new PVector(0,0);
void moveRoot(float x, float y, float newTheta) {
float halfLength = length/2;
halfway = new PVector(root.x+(halfLength*cos(theta)), root.y+(halfLength*sin(theta)), 0.0);
PVector pull = PVector.sub(tip, halfway);
tip = PVector.add(halfway, pull);
PVector straightTip = new PVector(root.x+(length*cos(theta)), root.y+(length*sin(theta)), 0.0);
PVector toward = PVector.sub(straightTip, tip);
float distance = toward.mag();
toward.limit(distance/100);
void seek(PVector attractor) {
float attractReach = 250;
float distance = PVector.dist(tip, attractor);
if (distance <= attractReach) {
PVector toward = PVector.sub(attractor, tip);
toward.mult(20/distance);
toward.mult(pow(1-(distance/attractReach),1)*1);
void render(color strandColor) {
line(root.x,root.y, halfway.x,halfway.y);
line(halfway.x,halfway.y, tip.x,tip.y);
bezier(root.x,root.y, halfway.x,halfway.y, halfway.x,halfway.y, tip.x,tip.y);