//=====================================//
// Outcat by Brian Ho //
//=====================================//
Player player = new Player();
Camera camera = new Camera();
float worldSize = 2000; //square world
boolean keyUp, keyLeft, keyRight, keyDown;
Animol[] animols = new Animol[24];
Group bears;
Group dogs;
Group fish;
Group cows;
//class Keys {boolean up = false; boolean down = false; boolean left = false; boolean right = false;}
//Keys keys = new Keys();
void setup(){
size(400,400,P2D);
smooth(5);
frameRate(60);
noStroke();
animols[0] = new Animol(150, -175);
animols[1] = new Animol(250, -155);
animols[2] = new Animol(130, -90);
animols[3] = new Animol(180, -60);
animols[4] = new Animol(70, -130);
animols[5] = new Animol(110, -185);
animols[6] = new Animol(60, 0);
Animol[] tempBears = {animols[0],animols[1],animols[2],animols[3],animols[4],animols[5],animols[6]};
bears = new Group(tempBears,0);
animols[7] = new Animol(-100, -145);
animols[8] = new Animol(-250, -70);
animols[9] = new Animol(-135, -90);
animols[10] = new Animol(-115, -43);
animols[11] = new Animol(-180, -130);
animols[12] = new Animol(-215, -140);
Animol[] tempDogs = {animols[7],animols[8],animols[9],animols[10],animols[11],animols[12]};
dogs = new Group(tempDogs,1);
animols[13] = new Animol(-400, -145);
animols[14] = new Animol(-350, -270);
animols[15] = new Animol(-437, -290);
animols[16] = new Animol(-380, -243);
animols[17] = new Animol(-460, -330);
animols[18] = new Animol(-415, -340);
Animol[] tempFish = {animols[13],animols[14],animols[15],animols[16],animols[17],animols[18]};
fish = new Group(tempFish,2);
animols[19] = new Animol(100, 245);
animols[20] = new Animol(57, 270);
animols[21] = new Animol(137, 290);
animols[22] = new Animol(-50, 220);
animols[23] = new Animol(80, 330);
Animol[] tempCows = {animols[19],animols[20],animols[21],animols[22],animols[23]};
cows = new Group(tempCows,3);
}
void draw(){
//update all
player.update();
camera.position = new PVector(player.position.x - 200,player.position.y - 200);
bears.update();
dogs.update();
fish.update();
cows.update();
for(int i = 0; i < animols.length; i++){
animols[i].update();
}
//drawing
background(0);
createParallaxGrid();
bears.display();
dogs.display();
fish.display();
cows.display();
for(int i = 0; i < animols.length; i++){
animols[i].display();
}
player.display();
}
void createParallaxGrid(){
//small grid
float gridSize = 90;
float parallaxCoef = 0.5;
for(float y = 0; y < worldSize*2; y += gridSize){
for(float x = 0; x < worldSize*2; x += gridSize){
PVector gridPos = camera.camCoords(new PVector(x-worldSize/2,y-worldSize/2));
fill(40);
ellipse(gridPos.x*parallaxCoef, gridPos.y*parallaxCoef, 5, 5);
}
}
//large grid
gridSize = 40;
for(float y = 0; y < worldSize; y += gridSize){
for(float x = 0; x < worldSize; x += gridSize){
PVector gridPos = camera.camCoords(new PVector(x-worldSize/2,y-worldSize/2));
fill(70);
ellipse(gridPos.x, gridPos.y, 7, 7);
}
}
}
//Input handling
void keyReleased(){
if(keyCode == UP) keyUp = false;
if(keyCode == DOWN) keyDown = false;
if(keyCode == LEFT) keyLeft = false;
if(keyCode == RIGHT) keyRight = false;
}
void keyPressed(){
if(keyCode == UP) keyUp = true;
if(keyCode == DOWN) keyDown = true;
if(keyCode == LEFT) keyLeft = true;
if(keyCode == RIGHT) keyRight = true;
}
float clamp(float value, float min, float max){
if(value > max) return max;
else if(value < min) return min;
else return value;
}class Animol{
public PVector position = new PVector(0,0);
PVector speed = new PVector(0,0);
float acel = 0.5;
float maxSpeed = .8;
float fricCoef = 0.85;
PVector toPlayer;
PVector lookDir;
Group group;
PVector wanderDir = new PVector(random(1)-.5,random(1)-.5);
Animol(float x, float y){
position.x = x;
position.y = y;
}
public void update(){
toPlayer = new PVector(player.position.x - position.x, player.position.y - position.y);
switch(group.stage){
case 3 :
lookDir = new PVector(group.center().x - position.x, group.center().y - position.y); //look at eachother if in stage 3
break;
case 2 :
lookDir = group.moveDirection;
//maxSpeed = 300/(toPlayer.mag()+100);
speed.add(group.moveDirection);
speed.add(wanderDir);
break;
case 1:
lookDir = new PVector(player.position.x - position.x, player.position.y - position.y); //look at player if in stage 1;
break;
}
lookDir.normalize();
lookDir.mult(3);
//impose speed limit
if(speed.mag() > maxSpeed){
speed.normalize();
speed.mult(maxSpeed);
}
position.add(speed);
speed.mult(fricCoef);
}
public void display(){
PVector camPos = camera.camCoords(position);
ellipseMode(CENTER);
fill(255);
ellipse(camPos.x,camPos.y,25,25);
switch(group.species){
case 0 : //bear
ellipse(camPos.x +9, camPos.y -9, 6, 6);
ellipse(camPos.x -9, camPos.y -9, 6, 6);
break;
case 1 : //dog
triangle(camPos.x - 18, camPos.y - 10, camPos.x - 12, camPos.y - 10, camPos.x - 16, camPos.y - 1);
triangle(camPos.x + 18, camPos.y - 10, camPos.x + 12, camPos.y - 10, camPos.x + 16, camPos.y - 1);
break;
case 2 : //fish
triangle(camPos.x - 14, camPos.y + 15, camPos.x - 10, camPos.y + 15, camPos.x - 10, camPos.y + 8);
triangle(camPos.x + 14, camPos.y + 15, camPos.x + 10, camPos.y + 15, camPos.x + 10, camPos.y + 8);
triangle(camPos.x + 14, camPos.y, camPos.x + 20 + sin(frameCount*.2), camPos.y - 6, camPos.x+20 + sin(frameCount*.2), camPos.y+6);
break;
case 3 : //goat
ellipse(camPos.x +10, camPos.y -9, 8, 4);
ellipse(camPos.x -10, camPos.y -9, 8, 4);
triangle(camPos.x - 10, camPos.y-5, camPos.x-6,camPos.y - 15, camPos.x-1, camPos.y-10);
triangle(camPos.x + 10, camPos.y-5, camPos.x+6,camPos.y - 15, camPos.x+1, camPos.y-10);
break;
}
fill(0);
//eyes face in speed direction
ellipse(camPos.x - 4 + lookDir.x, camPos.y + lookDir.y,3,3);
ellipse(camPos.x + 4 + lookDir.x, camPos.y + lookDir.y,3,3);
if(group.stage == 3){ //content face
//mouth
rectMode(CENTER);
ellipse(camPos.x - 2 + lookDir.x, camPos.y +6 + lookDir.y,4,4);
ellipse(camPos.x + 2 + lookDir.x, camPos.y +6 + lookDir.y,4,4);
fill(255);
ellipse(camPos.x - 2 + lookDir.x, camPos.y +5 + lookDir.y,3,3);
ellipse(camPos.x + 2 + lookDir.x, camPos.y +5 + lookDir.y,3,3);
}
else if(group.stage == 1){ //discontent face
fill(0);
rect(camPos.x+lookDir.x, camPos.y+lookDir.y+6,1,3);
rect(camPos.x+lookDir.x, camPos.y+lookDir.y+8,5,1);
}
else if(group.stage == 2){ //GRRR angery
//eyebrows
stroke(0);
line(camPos.x+lookDir.x - 8, camPos.y+lookDir.y-6,camPos.x+lookDir.x - 2,camPos.y+lookDir.y-4);
line(camPos.x+lookDir.x + 8, camPos.y+lookDir.y-6,camPos.x+lookDir.x + 2,camPos.y+lookDir.y-4);
noStroke();
//mouth
fill(0);
rect(camPos.x+lookDir.x, camPos.y+lookDir.y+6,1,3);
triangle(camPos.x+lookDir.x, camPos.y+lookDir.y+5,camPos.x+lookDir.x+4, camPos.y+lookDir.y+8,camPos.x+lookDir.x-4, camPos.y+lookDir.y+8);
fill(255);
triangle(camPos.x+lookDir.x, camPos.y+lookDir.y+7,camPos.x+lookDir.x+4, camPos.y+lookDir.y+10,camPos.x+lookDir.x-4, camPos.y+lookDir.y+10);
}
//nose
fill(0);
ellipse(camPos.x+lookDir.x, camPos.y+lookDir.y+4,4,2);
}
public void setGroup(Group _group){
this.group = _group;
}
public void genWanderDir(){
wanderDir = new PVector(random(1)-.5,random(1)-.5);
}
}class Camera {
PVector position = new PVector(200,200);
public PVector camCoords(PVector world){
return new PVector( world.x - position.x, world.y - position.y);
}
}class Group { //helps the cliques organise themselves
public Animol[] groupMembers;
ArrayList<Triangle> tris = new ArrayList<Triangle>();
private Timer stage1Timer = new Timer(2500); //1 second
public int stage = 3;
public int species = 1;
public PVector moveDirection = new PVector(0, 0);
PVector stage2PlayerPoint;
Group(Animol[] animols, int spec) {
species = spec;
groupMembers = animols;
for (Animol animol : groupMembers) {
animol.setGroup(this);
}
generateAllTriangles();
}
public void display() {
stroke(255, 100, 100, 140);
if (stage == 1 || stage == 3) {
for (Triangle tri : tris) {
tri.display();
}
}
noStroke();
}
public void update() {
stage1Timer.update();
PVector myCenter=center();
myCenter.sub(player.position);
float myCenterMag=myCenter.mag();
if (stage == 3 && myCenterMag < 300) {
for (Triangle tri : tris) {
//check for collision
if (tri.getLineAB().circleCollision(player.position, 13)||
tri.getLineBC().circleCollision(player.position, 13)||
tri.getLineCA().circleCollision(player.position, 13)) {
stage = 1;
stage1Timer.start();
}
}
}
//start stage 2
if (stage1Timer.finished && stage == 1) {
stage = 2;
player.avoidTriggers ++;
//stage2PlayerPoint = player.position.copy();
stage2PlayerPoint = new PVector(player.position.x, player.position.y);
//moveDirection = center().sub(stage2PlayerPoint).normalize();
PVector moveDirection=center();
moveDirection.sub(stage2PlayerPoint);
moveDirection.normalize();
for (Animol animol : groupMembers) {
animol.genWanderDir();
}
}
//stage 2
if (stage == 2) {
//float toCenter = center().sub(stage2PlayerPoint).mag();
PVector toCenterVector=center();
toCenterVector.sub(stage2PlayerPoint);
float toCenter=toCenterVector.mag();
//condition to switch to stage 3
if (stage == 2 && toCenter > 300) {
stage = 3;
generateAllTriangles();
}
}
}
public PVector center() {
float totalX = 0;
float totalY = 0;
for (Animol animol : groupMembers) {
totalX += animol.position.x;
totalY += animol.position.y;
}
return new PVector(totalX/groupMembers.length, totalY/groupMembers.length);
}
public Animol closest(PVector point) {
float leastMag = 2000;
Animol closestOne = null;
for (Animol animol : groupMembers) {
if (point.dist(animol.position) < leastMag) {
leastMag = point.dist(animol.position);
closestOne = animol;
}
}
return closestOne;
}
public void generateAllTriangles() {
tris = new ArrayList<Triangle>();
for (int a = 0; a < groupMembers.length; a++) {
PVector posA = groupMembers[a].position;
for (int b = 0; b < groupMembers.length; b++) {
PVector posB = groupMembers[b].position;
if (posB == posA) break;
for (int c = 0; c < groupMembers.length; c++) {
PVector posC = groupMembers[c].position;
if (posC == posA || posC == posB) break;
Triangle dTris = new Triangle(posA, posB, posC);
PVector center = dTris.getCircumcircleCenter();
fill(0, 0, 0, 0);
float radius = center.dist(posA);
boolean delauneyCircumcircle = true;
for (int p = 0; p < groupMembers.length; p++) {
if (groupMembers[p].position.dist(center) < radius * .99) {
delauneyCircumcircle = false;
break;
}
}
if (delauneyCircumcircle) {
tris.add(dTris);
}
}
}
}
}
}class Line {
PVector a;
PVector b;
Line(PVector _a, PVector _b) {
a = _a;
b = _b;
}
public void display() {
PVector camA = camera.camCoords(a);
PVector camB = camera.camCoords(b);
line(camA.x, camA.y, camB.x, camB.y);
}
public float slope() {
float maxX = (b.x > a.x) ? b.x : a.x;
float minX = (b.x > a.x) ? a.x : b.x;
float maxY = (b.y > a.y) ? b.y : a.y;
float minY = (b.y > a.y) ? a.y : b.y;
return (maxX - minX)/(maxY - minY);
}
public float parallelSlope() {
float maxX = (b.x > a.x) ? b.x : a.x;
float minX = (b.x > a.x) ? a.x : b.x;
float maxY = (b.y > a.y) ? b.y : a.y;
float minY = (b.y > a.y) ? a.y : b.y;
return -(maxY - minY)/(maxX - minX);
}
public float yInt(float slope) {
// c = y - mx
return(a.y - slope*a.x);
}
//uses this tutorial: https://code.tutsplus.com/tutorials/quick-tip-collision-detection-between-a-circle-and-a-line--active-10546
public boolean circleCollision(PVector position, float radius) {
boolean collides = false;
//uses vector projection to project the perpendicular distance of the circle
//PVector distanceVectorA = position.copy().sub(a);
//PVector line = b.copy().sub(a);
//PVector perpLine = line.copy().normalize().rotate(HALF_PI);
PVector distanceVectorA = new PVector(position.x, position.y);
distanceVectorA.sub(a);
PVector line = new PVector(b.x, b.y);
line.sub(a);
PVector perpLine = new PVector(line.x, line.y);
perpLine.normalize();
perpLine.rotate(HALF_PI);
//dot product of normal and distance. This is the projection.
float distanceToLine = perpLine.x * distanceVectorA.x + distanceVectorA.y * perpLine.y;
//checks to see if the point is inside the line.
//PVector distanceVectorB = position.copy().sub(b);
PVector distanceVectorB = new PVector(position.x, position.y);
distanceVectorB.sub(b);
float dotAP = line.x * distanceVectorA.x + distanceVectorA.y * line.y;
float dotBP = line.x * distanceVectorB.x + distanceVectorB.y * line.y;
//if the distance to the center is less than the radius then it collides
//if the dot products are like this then in means that the point is within the line. Not sure how this works, but it does.
if (Math.abs(distanceToLine) < radius && (dotAP >= 0 && dotBP <= 0))
collides = true;
return collides;
}
}class Player {
public PVector position = new PVector(0, 0);
PVector speed = new PVector(0, 0);
float acel = 0.5;
float maxSpeed = 2.2;
float fricCoef = 0.87;
public int avoidTriggers = 0;
PVector lookDir;
public void update() {
if (keyUp) speed.y -= acel;
if (keyDown) speed.y += acel;
if (keyLeft) speed.x -= acel;
if (keyRight) speed.x += acel;
//impose speed limit
if (speed.mag() > maxSpeed) {
speed.normalize();
speed.mult(maxSpeed);
}
position.add(speed);
speed.mult(fricCoef);
lookDir=new PVector(speed.x, speed.y);
lookDir.normalize();
//lookDir = speed.copy().normalize();
lookDir.mult(3);
}
public void display() {
PVector camPos = camera.camCoords(position);
ellipseMode(CENTER);
fill(255);
ellipse(camPos.x, camPos.y, 25, 25);
triangle(camPos.x - 12, camPos.y-5, camPos.x-14, camPos.y - 15, camPos.x-1, camPos.y-10);
triangle(camPos.x + 12, camPos.y-5, camPos.x+14, camPos.y - 15, camPos.x+1, camPos.y-10);
drawExpression();
}
void drawExpression() {
PVector camPos = camera.camCoords(position);
fill(0);
//eyes face in speed direction
ellipse(camPos.x - 4 + lookDir.x, camPos.y + lookDir.y, 3, 3);
ellipse(camPos.x + 4 + lookDir.x, camPos.y + lookDir.y, 3, 3);
if (avoidTriggers < 1) {
//mouth
rectMode(CENTER);
ellipse(camPos.x - 2 + lookDir.x, camPos.y +6 + lookDir.y, 4, 4);
ellipse(camPos.x + 2 + lookDir.x, camPos.y +6 + lookDir.y, 4, 4);
fill(255);
ellipse(camPos.x - 2 + lookDir.x, camPos.y +5 + lookDir.y, 3, 3);
ellipse(camPos.x + 2 + lookDir.x, camPos.y +5 + lookDir.y, 3, 3);
} else if (avoidTriggers < 25) {
//eyebrows
stroke(0);
line(camPos.x+lookDir.x - 6, camPos.y+lookDir.y-3, camPos.x+lookDir.x - 3, camPos.y+lookDir.y-6);
line(camPos.x+lookDir.x + 6, camPos.y+lookDir.y-3, camPos.x+lookDir.x + 3, camPos.y+lookDir.y-6);
noStroke();
//mouth
fill(0);
rect(camPos.x+lookDir.x, camPos.y+lookDir.y+6, 1, 3);
triangle(camPos.x+lookDir.x, camPos.y+lookDir.y+5, camPos.x+lookDir.x+4, camPos.y+lookDir.y+8, camPos.x+lookDir.x-4, camPos.y+lookDir.y+8);
fill(255);
triangle(camPos.x+lookDir.x, camPos.y+lookDir.y+7, camPos.x+lookDir.x+4, camPos.y+lookDir.y+10, camPos.x+lookDir.x-4, camPos.y+lookDir.y+10);
} else { // if you do it 25 times you become angry
//eyebrows
stroke(0);
line(camPos.x+lookDir.x - 6, camPos.y+lookDir.y-6, camPos.x+lookDir.x - 3, camPos.y+lookDir.y-3);
line(camPos.x+lookDir.x + 6, camPos.y+lookDir.y-6, camPos.x+lookDir.x + 3, camPos.y+lookDir.y-3);
noStroke();
//mouth
fill(0);
rect(camPos.x+lookDir.x, camPos.y+lookDir.y+6, 1, 3);
triangle(camPos.x+lookDir.x, camPos.y+lookDir.y+5, camPos.x+lookDir.x+4, camPos.y+lookDir.y+8, camPos.x+lookDir.x-4, camPos.y+lookDir.y+8);
fill(255);
triangle(camPos.x+lookDir.x, camPos.y+lookDir.y+7, camPos.x+lookDir.x+4, camPos.y+lookDir.y+10, camPos.x+lookDir.x-4, camPos.y+lookDir.y+10);
}
//nose
fill(0);
ellipse(camPos.x+lookDir.x, camPos.y+lookDir.y+4, 4, 2);
}
}class Timer{
int timeTo;
int totalTime;
boolean started = false;
public boolean finished = false;
Timer(int timeInMillis){
totalTime = timeInMillis;
}
public void update(){
if(started && !finished){
if(millis() >= timeTo){
finished = true;
started = false;
}
}
}
public void start(){
if(started == false){
started = true;
timeTo = millis() + totalTime;
finished = false;
}
}
}class Triangle{
public PVector a;
public PVector b;
public PVector c;
private Line ab;
private Line bc;
private Line ca;
Triangle(PVector _a, PVector _b, PVector _c){
a = _a;
b = _b;
c = _c;
ab = new Line(a,b);
bc = new Line(b,c);
ca = new Line(c,a);
}
public void display(){
getLineAB().display();
getLineBC().display();
getLineCA().display();
}
public PVector getCircumcircleCenter(){
Line line1 = getLineAB();
Line line2 = getLineBC();
PVector line1MidPoint = new PVector((line1.a.x + line1.b.x)/2, (line1.a.y + line1.b.y)/2);
PVector line2MidPoint = new PVector((line2.a.x + line2.b.x)/2, (line2.a.y + line2.b.y)/2);
float slope1 = -1*(line1.b.x - line1.a.x)/(line1.b.y - line1.a.y);
float slope2 = -1*(line2.b.x - line2.a.x)/(line2.b.y - line2.a.y);
float yint1 = line1MidPoint.y - slope1*line1MidPoint.x;
float yint2 = line2MidPoint.y - slope2*line2MidPoint.x;
float ix = (yint2 - yint1)/(slope1 - slope2);
float iy = slope1 * ix + yint1;
return new PVector(ix,iy);
}
public Line getLineAB(){
ab = new Line(a,b);
return ab;
}
public Line getLineBC(){
bc = new Line(b,c);
return bc;
}
public Line getLineCA(){
ca = new Line(c,a);
return ca;
}
}