// the monster class builds Typomonsters!
class Monster {
float x, y;
int numType; // number of characters in flock
int flockSize; // physical size of flock
float typeSize; // physical size of characters
float avgSpeed; // average speed of characters
int emotion = 0; // initial emotion
Type[] t;
Monster(float xpos, float ypos, int nt, int fs, float ts, float as) {
x = xpos;
y = ypos;
numType = nt;
t = new Type[numType];
flockSize = fs;
typeSize = ts;
avgSpeed = as;
for (int i=0; i<t.length; i++) {
int num = i;
float angle = random(-TWO_PI, TWO_PI);
float rx = random(flockSize/2);
float ry = random(flockSize/3);
t[i] = new Type(x+rx*cos(angle), y+ry*sin(angle), (random(avgSpeed-1.0)+1.0), typeSize, num);
}
}
void update() {
for (int i=0; i<t.length; i++) {
t[i].update();
t[i].display();
}
}
int getEmotion() {
int e = int(random(7));
return e;
}
void setString(int[] c) {
// iterate through string code and find characters
if (c.length<=t.length) {
int j=0;
while (j<c.length) {
for (int i=0; i<t.length; i++){
if (t[i].getShapeNum() == c[j] && !t[i].inString){
t[i].incSize();
t[i].setTargetPos(j, c.length);
t[i].setInString();
j++;
break;
}
}
}
}
}
void resetFlock() {
// reset the whole flock to a new position
int randomFlockSize = int(random(flockSize*0.8, flockSize*1.2));
x = map(random(width), 0, width, width/6, width - width/6); // randomize x position
y = map(random(height), 0, height, height/6, height - height/6); // randomize y position
if (t[0] != null) {
for (int i=0; i<t.length; i++) {
t[i].reset(x, y, int(random(randomFlockSize)), avgSpeed);
}
}
}
}
// The particle class controls all movements & transformation
class Particle {
float x, y, dx, dy, tx, ty, px, py, speed, tangle;
float angle = random(-0.3, 0.3);
float terrRadius = random(20, 80); // territory radius, used for backOff()
float initSize, typeSize, tSize; // tSize = target size
float maxSize, charSpace; // sets spacing for displayed text
float maxSizeScalar = 4.0; // sets the maximum size
float resizeSpd = 0.05;
boolean inString = false; // set if the character is used to display text
Particle(float xpos, float ypos, float spd, float s) {
x = xpos;
y = ypos;
tx = xpos;
ty = ypos;
px = xpos;
py = ypos;
speed = spd;
initSize = s;
tSize = initSize;
maxSize = initSize*maxSizeScalar;
charSpace = initSize;
}
void update() {
dx = mouseX - x;
dy = mouseY - y;
float d = dist(x, y, mouseX, mouseY);
// back off when mouse comes too close
if (d < terrRadius) {
backOff();
}
else {
// idle movement: move sideways to cursor, random vertical movement
idleMovement();
}
// attack
if (mousePressed) {
attack();
}
// update size
if (abs(typeSize-tSize)>0.1) {
typeSize += (tSize - typeSize)*resizeSpd;
}
// store previous position
px = x;
py = y;
}
void reset(float x, float y, int fs, float as){
float angle = random(-TWO_PI, TWO_PI);
float rx = random(fs/2);
float ry = random(fs/3);
speed = random(as)+1.0;
tx = x+rx*cos(angle);
ty = y+ry*sin(angle);
inString = false;
decSize();
}
void backOff(){
angle = (atan2(dy, dx)+PI);
x += cos(angle) * speed * random(1, 2);
y += sin(angle) * speed * random(1, 2);
}
void idleMovement(){
// slow movement towards mouseX
if (!inString) {
tx += (mouseX - tx)*0.001+random(-3, 3);
ty += (mouseY - ty)*0.001+random(-3, 3);
// random movement
tx += random(-1, 1)*0.5;
ty += random(-1, 1)*0.2;
}
else {
// random movement
tx += random(-1, 1);
ty += random(-1, 1);
}
x += (tx - x)*speed*0.01;
y += (ty - y)*speed*0.01;
}
void attack(){
tangle = atan2(dy, dx);
angle += (tangle-angle)*0.8;
x += cos(angle) * speed * random(1, 6);
y += sin(angle) * speed * random(1, 6);
tSize = initSize;
}
void setTargetPos(int c, int s) {
int charNum = c;
int stringLength = s;
tx = width/2 - stringLength*charSpace/2 + charSpace*c;
ty = height/2 - tSize*2;
}
void incSize() {
if (tSize < maxSize) {
tSize += maxSize-initSize;
charSpace = tSize/1.5;
}
}
void decSize() {
if (typeSize > initSize) {
tSize = initSize;
charSpace = initSize;
}
}
void setInString() {
inString = true;
}
}
// Character drawing
class Type extends Particle {
float headAngle;
int typeNum, shapeNum;
Type(float ix, float iy, float spd, float s, int n) {
super(ix, iy, spd, s);
typeNum = n;
shapeNum = typeNum%NUM_TYPESHAPES;
}
void update() {
super.update();
if (super.inString) {
headAngle = 0;
} else {
headAngle = atan2(dy, dx) - HALF_PI; // Make 'heads' follow the cursor
}
}
void display() {
pushMatrix();
translate(x, y);
rotate(headAngle);
pushMatrix();
scale(typeSize/min(dist(x, y, mouseX, mouseY)+1, 10));
shape(typeShape[shapeNum], 0, 0);// Draw shapes
popMatrix();
popMatrix();
}
int getShapeNum() {
return shapeNum;
}
}
// TYPOMONSTERS
// GRRRR!
// GLOBAL CONSTANTS
int NUM_MONSTERS = 3; // number of monster instances
int NUM_TYPESHAPES = 41; // size of the characterset
int AVG_FLOCKSIZE = 164;
float AVG_TYPESIZE = 6.0;
float AVG_SPEED = 3.0;
int RESET_TIMER = 10000; // reset a flock automatically after a specified time
Monster[] m = new Monster[NUM_MONSTERS];
PShape[] typeShape = new PShape[NUM_TYPESHAPES];
char[] charSet = {
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '.', ',', '!', '?', ' '};
// Character to integer mapping, I should have used ASCII encoding for this…
//'0', '1', '2', '3', '4', '5', '6', '7', '8', '9','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24','25','26','27','28','29','30','31','32','33','34','35','36','37','38','39','40'
int idleTime, pIdleTime, actMonster;
void setup() {
size(480, 320); // iPhone size ;-)
smooth();
noStroke();
for (int i=0; i<m.length; i++) {
float x = map(random(width), 0, width, width/6, width - width/6); // randomize x position
float y = map(random(height), 0, height, height/6, height - height/6); // randomize y position
int numType = NUM_TYPESHAPES*5; // multiplier sets amount of equal characters in flock
int flockSize = int(random(AVG_FLOCKSIZE/2, AVG_FLOCKSIZE*1.5)); // randomize flocksize
float typeSize = (AVG_TYPESIZE*flockSize/AVG_FLOCKSIZE); // let flocksize determine typesize
m[i] = new Monster(x, y, numType, flockSize, typeSize, AVG_SPEED); // construct monsters
}
for (int j=0; j<NUM_TYPESHAPES; j++) { // load array of shapes for type
String shapeName = "t" + j + ".svg";
typeShape[j] = loadShape(shapeName);
}
}
void draw() {
background(255);
for (int i=0; i<m.length; i++) {
m[i].update();
}
idleTime = millis() - pIdleTime;
if (idleTime > RESET_TIMER) {
resetIdleTimer();
m[actMonster].resetFlock(); // reset active monster
}
}
void mouseReleased() {
resetIdleTimer();
// determine which monster to reset
float[] monsterDist = new float[m.length]; // create an array for storing the distances from the monsters to the mouse
for (int i=0; i<monsterDist.length; i++) {
monsterDist[i] = dist(mouseX, mouseY, m[i].x, m[i].y);
}
actMonster = minItem(monsterDist); // test which monster is closest and set it to be active
m[actMonster].resetFlock(); // reset active monster
String s;
switch(m[actMonster].getEmotion()) {
case 0:
s = "i am typomonster!";
break;
case 1:
s = "kern me if you can";
break;
case 2:
s = "helvetica sucks!";
break;
case 3:
s = "do you like my spacing?";
break;
case 4:
s = "comic sans lover";
break;
default:
s = "hehehe";
break;
}
m[actMonster].setString(stringToCode(s)); // display string
}
// convert a String to an array of integers
int[] stringToCode(String s) {
int[] code = new int[s.length()];
for (int i=0; i<s.length();i++) {
for (int j=0; j<charSet.length;j++) {
if (s.charAt(i)==charSet[j]) {
code[i] = j;
}
}
}
return code;
}
int minItem(float[] testArray) {
float minimum = testArray[0];
int item = 0;
for(int i = 1; i < testArray.length; i++) {
if (testArray[i] < minimum) {
minimum = testArray[i];
item = i;
}
}
return item;
}
void resetIdleTimer() {
pIdleTime += idleTime;
idleTime = 0;
}