Your browser does not support the canvas tag.

previous        Show / Hide Source        Download        next
/*Space Tentacles
 By Zachary MacLeod
 
 Oh look, there's a GIANT SPACE HORROR IN FRONT OF YOU
 Good thing you have a HUGE LASER BEAM
 Fire it by holding the mouse and WATCH HIM WRITHE
 Moving the mouse around pilots your ship!
 */

float frameCount =0; //how many frames has it been running for?

float shipX=300; //ship's location
float shipY=300; 

float taper; //how much the tentacles shrink in size
float len; //how many segments long are the tentacles
float segment; //current segment being drawn
float tentangle; //angle of the tentacle segment
float wiggles; //how many wiggles (half a sin wave) are in the tentacle 
float bend; //how much is the tentacle bending
float wiggleSize; //how big are the wiggles (more extreme sin wave height)
float curl; //how much is the tentacle curling at the tip
float wiggleStart; //how far along the sinwave does the tentacle start

float ouch; //how bad does it hurt

color startColor; //starting color for tentacles
color endColor; //ending color for tentacles

float laserCharge; //how charged is the laser
boolean laser; //is the laser firing

float redRat; //ratio used for flashing red

void setup() {
  frameCount=10000f; //start with some offset so the tentacls don't begin linked up
  size(400, 400);
}




void draw() {
  //clear();
  background(0);

  redRat = myLerp(1, 1-pow(frameCount%6f, 0.5)/pow(6, 0.5), ouch); //calculate redRat. This does both the red flashing (based off of frame count), and the 

  //use the same random seed for the stars so they don't fling around
  randomSeed(666);
  for (int i=0; i<140; i++) {


    fill(255);
    ellipse(random(600), ((random(0.15f)+0.5f)*frameCount)%600, 2, 2);
  }


  //back tentacles
  drawBackTentacles();

  //aux tentacles
  drawAuxTentacles();


  //handle the ship
  shipX=myLerp(shipX, mouseX, 0.1f);
  shipY=myLerp(shipY, mouseY, 0.1f);
  float shipAngle=((shipX-mouseX)/300f)*PI/4+PI/2;

  drawShip(shipX, shipY, shipAngle);



  //increase frame count
  frameCount++;

  //Laser Stuff
  if (laser) {
    drawLaser(shipX, shipY, shipAngle);
    laserCharge=min(laserCharge+1, 100f);
    if (laserCharge>40f) {
      ouch=myLerp(ouch, 1f, 0.25f);
    }
  } else {
    laserCharge=max(laserCharge-4, 0f);
    drawLaserExhaust(shipX, shipY, shipAngle);
    ouch=myLerp(ouch, 0f, 0.25f);
  }
  //Draw The Body
  drawBody(parx(35));

  //front tentacles
  drawFrontTentacles();
  drawEye(200+parx(35), 105, 0.6f);
  drawEye(140+parx(35), 35, 0.3f);
  drawEye(260+parx(35), 35, 0.3f);
  drawEye(40+parx(35), 45, 0.5f);
  drawEye(360+parx(35), 45, 0.5f);
}


void drawBackTentacles() {

  for (int i=0; i<15; i++) {
    //use a seeded semi-random number generator to pick random values but keep them consistent. Not the best random numbers but its good enough.
    drawTentacle(i*width/15+parx(5), 40+sin(PI*i/15)*20, //x,y
      15, 55+semiSeed(9*i)*15+sin(PI*i/15)*25, //size, length
      PI/2, myLerp(sinscale(semiSeed(i)*20+90, semiSeed(12*i)*0.1f+0.14f), PI/2-angleToPoint(i*width/20+parx(5), 80+sin(PI*i/20)*30, shipX, shipY), ouch), //angle,bend
      myLerp(sinscale(semiSeed(3.4*i)*450+550, 5f), 8f, ouch), myLerp(semiSeed(666f*i)*0.7f, semiSeed(666f*i)*0.2f+0.3f, ouch), (frameCount/20f)%TWO_PI+semiSeed(7*i)*1000, //wiggles, wigglesize, wigglestart
      0f, color(150, 120*redRat, 140*redRat), color(5, 0, 5*redRat)); //curl, start color, end color
  }
}

//draws a single lump of the body
void drawBodyLump(float x, float y, float hor, float ver, float colorScale, int isOutline) {
  //draw the outline first
  if (isOutline == 1) {
    stroke(20, 0, 20);
    strokeWeight(3);
    fill(20, 0, 20);
    ellipse(x, y, hor, ver);
  } else {
    noStroke();
    fill(194*colorScale, 114*redRat*colorScale, 129*redRat*colorScale);
    ellipse(x, y, hor, ver);
    if (laserCharge>40) {
      noStroke();
      fill(0, 0, 0, 25);
      float dir = angleToPoint(x, y, shipX, shipY)-PI;
      for (int i = 0; i<10; i++) {
        arc(x, y, hor, ver, dir-PI*(i/10f), dir+PI*(i/10f));
      }
    }
  }
}

void drawAuxTentacles() {

  //leftAux
  drawTentacle(70+parx(25), 75, //x, y
    40, sinscale(352f, 15f)+40, //size, length
    myLerp(PI/2, PI/1.5, ouch), sinscale(90f, 0.15f), //angle, bend
    sinscale(134, 2f)+6f, sinscale(134, 0.7f), myLerp((frameCount/18f)%TWO_PI, frameCount/2f%TWO_PI, ouch), //wiggles, wigglesize, wigglestart
    myLerp(sinscale(203f, 2f), 0, ouch), color(220, 170*redRat, 160*redRat), color(70, 20*redRat, 50*redRat)); //curl, start color, end color

  //rightAux
  //slightly different values used to keep them un-synced
  drawTentacle(330+parx(25), 75, //x, y
    40, sinscale(338f, 15f)+40, //size, length
    myLerp(PI/2, PI/4, ouch), sinscale(-85f, 0.15f), //angle, bend 
    sinscale(154, 2f)+6f, sinscale(164, 0.7f), myLerp((frameCount/17f)%TWO_PI, frameCount/2f%TWO_PI, ouch), //wiggles, wigglesize, wigglestart
    myLerp(sinscale(184f, -2f), 0, ouch), color(220, 170*redRat, 160*redRat), color(70, 20*redRat, 50*redRat));//curl, start color, end color
}



void drawFrontTentacles() {
  drawTentacle(135+parx(35), 125, //x, y
    50, myLerp(sinscale(286f, 10f)+30, 40, ouch), //size, length
    PI/2, myLerp(sinscale(93f, 0.15f), horrorToShip()/4, ouch), //angle, bend
    myLerp(sinscale(164, 2f)+6f, 10f, ouch), sinscale(74, 0.9f), myLerp((frameCount/20f)%TWO_PI, frameCount%TWO_PI, ouch), //wiggles, wigglesize, wigglestart
    myLerp(sinscale(194f, 1.8f), 0, ouch), color(240, 170*redRat, 160*redRat), color(120, 20*redRat, 90*redRat));//curl, start color, end color


  drawTentacle(265+parx(35), 125, //x, y
    50, myLerp(sinscale(294f, 10f)+30, 40, ouch), //size, length
    PI/2, myLerp(sinscale(-82f, -0.15f), -horrorToShip()/4, ouch), //angle, bend
    myLerp(sinscale(177, 2f)+6f, 10f, ouch), myLerp(sinscale(94, 0.9f), 0.4f, ouch), myLerp((frameCount/21f)%TWO_PI, frameCount%TWO_PI, ouch), //wiggles, wigglesize, wigglestart
    myLerp(sinscale(174f, -1.8f), 0, ouch), color(240, 170*redRat, 160*redRat), color(120, 20*redRat, 90*redRat));//curl, start color, end color
}

void drawBody(float x) {
  //uses a for loop, to draw the outline first.
  for (int i=0; i<=1; i++) {
    drawBodyLump(200+x, 80, 220, 140, 1f, 1-i);
    drawBodyLump(85+x, 20, 80, 45, 1f, 1-i);
    drawBodyLump(315+x, 20, 80, 45, 1f, 1-i);
    drawBodyLump(10+x, 30, 150, 125, 0.9f, 1-i);
    drawBodyLump(390+x, 30, 150, 125, 0.9f, 1-i);
    drawBodyLump(90+x, 30, 70, 95, 0.9f, 1-i);
    drawBodyLump(310+x, 30, 70, 95, 0.9f, 1-i);
    drawBodyLump(200+x, -50, 360, 220, 0.8f, 1-i);
  }
}


void drawEye(float x, float y, float scale) {
  noStroke();
  //draw sclera
  for (int i=0; i<50; i++) {
    fill(lerpColor(color(50, 48*redRat, 38*redRat), color(139, 244*redRat, 128*redRat), i/50f));
    ellipse(x, y, pow(50f-i, 0.2f)/pow(50f, 0.2f)*130f*scale, pow(50f-i, 0.4f)/pow(50f, 0.4f)*130f*scale);
  }
  fill(255, 64);
  ellipse(x-130f/4f*scale, y-130f/9f*scale, 130/5f*scale, 130/6f*scale); //white reflection
  ellipse(x-130f/4f*scale, y-130f/8f*scale, 130/5f*scale, 130/4f*scale); //white reflection

  drawIris(x+18*scale*cos(myLerp(angleToPoint(x, y, shipX, shipY), (frameCount+x) % TWO_PI, ouch)), y+10*sin(myLerp(angleToPoint(x, y, shipX, shipY), (frameCount+x) % TWO_PI, ouch)), 40*scale, 80*scale, myLerp(24, 4, ouch)*scale);

  //draw lower inner Eyelid, same as outer eyelid but darker + squashed
  drawEyelid(x, y, 130*scale, 110*scale, myLerp(0.4f, sinscale(4f, 0.4f)+0.8f, ouch), 10, PI/2, PI/3+sinscale(200, PI/40f), color(154, 64*redRat, 109*redRat));

  //draw lower outer eyelid
  drawEyelid(x, y, 130*scale, 130*scale, myLerp(0.4f, sinscale(4f, 0.4f)+0.8f, ouch), 10, PI/2, PI/3+sinscale(200, PI/40f), color(204, 124*redRat, 139*redRat));

  //draw upper inner Eyelid
  drawEyelid(x, y, 130*scale, 110*scale, myLerp(0.4f, 1.2f, ouch), 10, -PI/2, myLerp(PI/2+sinscale(140, PI/40f), PI/2f-PI/10f, ouch), color(154, 64*redRat, 109*redRat));
  //draw upper outer Eyelid
  drawEyelid(x, y, 130*scale, 130*scale, myLerp(0.4f, 1.2f, ouch), 10, -PI/2, myLerp(PI/2+sinscale(140, PI/40f), PI/2f-PI/10f, ouch), color(204, 124*redRat, 139*redRat));
}


void drawIris(float x, float y, float hor, float ver, float iris) {
  fill(171, 244*redRat, 34*redRat);
  strokeWeight(3);
  stroke(84, 130*redRat, 34*redRat);
  arc(x+hor/5.3f, y, hor, ver, PI/2+PI/8, TWO_PI-PI/2-PI/8);
  arc(x-hor/5.3f, y, hor, ver, -(PI/2-PI/8), PI/2-PI/8);
  fill(41, 64*redRat, 14*redRat);
  ellipse(x, y, iris, iris);
}

void drawEyelid(float x, float y, float hor, float ver, float curve, float smoothness, float baseAngle, float halfArc, color Color) {
  for (int i=0; i<smoothness+1; i++) {
    float startRat = pow(i/smoothness, curve);
    float endRat = pow(1-(i/smoothness), curve);
    fill(Color);
    strokeWeight(2);
    stroke(0, 0, 0, 128);
    arc(x, y, hor, ver, baseAngle-halfArc*endRat, baseAngle+halfArc*startRat);
  }
}

void mousePressed() {
  laser=true;
}
void mouseReleased() {
  laser=false;
}

//after you release mouse key
void drawLaserExhaust(float x, float y, float angle) {
  if (laserCharge>0f) {
    strokeWeight(2f);
    stroke(200, 255, 255, 30);
    fill(235, 255, 255, 20);
    for (float i =0; i<min(40f, laserCharge); i+=7) {
      float rat = pow(0.9f, laserCharge-i);
      rat*=1-(min(laserCharge-80f, 0f));
      ellipse(x, y, (10+i/25)*rat, (10+i/25)*rat);
    }

    if (laserCharge>40f) {

      float rat = pow(1-(70f-min(70f, laserCharge))/(30f), -1f);
      fill(205, 255, 255, 140*1/rat);
      noStroke();
      quad(x+rotX(-4, 0, angle), y+rotY(-4, 0, angle), x+rotX(4, 0, angle), y+rotY(4, 0, angle), x+rotX(4+26*rat, -800, angle), y+rotY(4+26*rat, -800, angle), x+rotX(-4-26*rat, -800, angle), y+rotY(-4-26*rat, -800, angle));
      for (int i=0; i<25; i++) {
        fill(205, 255, 255, 15);
        quad(x+rotX(-4, 0, angle), y+rotY(-4, 0, angle), x+rotX(4, 0, angle), y+rotY(4, 0, angle), x+rotX(4+(26+i*3)*rat, -800, angle), y+rotY(4+(26+i*3)*rat, -800, angle), x+rotX(-4-(26+i*3)*rat, -800, angle), y+rotY(-4-(26+i*3)*rat, -800, angle));
      }
    }
  }
}

//laser including while charging
void drawLaser(float x, float y, float angle) {
  strokeWeight(2f);
  stroke(200, 255, 255, 128);
  fill(235, 255, 255, 60);
  for (float i =0; i<min(40f, laserCharge); i+=7) {
    float rat = pow(0.9f, laserCharge-i);
    rat*=1-(min(laserCharge-80f, 0f));
    ellipse(x, y, (40+i/2)*rat, (40+i/2)*rat);
  }

  if (laserCharge>40f) {

    float rat = pow(1-(70f-min(70f, laserCharge))/(30f), -2f);
    fill(205, 255, 255, 255*1/rat);
    noStroke();
    quad(x+rotX(-4, 0, angle), y+rotY(-4, 0, angle), x+rotX(4, 0, angle), y+rotY(4, 0, angle), x+rotX(4+26*rat, -800, angle), y+rotY(4+26*rat, -800, angle), x+rotX(-4-26*rat, -800, angle), y+rotY(-4-26*rat, -800, angle));
    for (int i=0; i<25; i++) {
      fill(205, 255, 255, 15);
      quad(x+rotX(-4, 0, angle), y+rotY(-4, 0, angle), x+rotX(4, 0, angle), y+rotY(4, 0, angle), x+rotX(4+(26+i*3)*rat, -800, angle), y+rotY(4+(26+i*3)*rat, -800, angle), x+rotX(-4-(26+i*3)*rat, -800, angle), y+rotY(-4-(26+i*3)*rat, -800, angle));
    }
    for (int i=0; i<15; i++) {
      fill(255, 255, 255, 35);
      quad(x+rotX(-4, 0, angle), y+rotY(-4, 0, angle), x+rotX(4, 0, angle), y+rotY(4, 0, angle), x+rotX(4+(26-i*3)*rat, -800, angle), y+rotY(4+(26-i*3)*rat, -800, angle), x+rotX(-4-(26-i*3)*rat, -800, angle), y+rotY(-4-(26-i*3)*rat, -800, angle));
    }

    int maxRays = 20;
    for (float i=0; i<maxRays; i++) {
      strokeWeight(1f);
      stroke(255, 255, 255, 53);
      float arc = TWO_PI/maxRays;
      float startRad = arc*i+frameCount*(PI/24f);
      float midRad = arc*(i+0.5f)+frameCount*(PI/24f);
      float endRad = arc*(i+1)+frameCount*(PI/24f);

      float startX = 50*cos(startRad);
      float startY = 20*sin(startRad);
      float midX = 250*cos(midRad);
      float midY = -100-20*cos(midRad);
      float endX = 50*cos(endRad);
      float endY = 20*sin(endRad);

      triangle(x+rotX(startX, startY, angle), y+rotY(startX, startY, angle), x+rotX(midX, midY, angle), y+rotY(midX, midY, angle), x+rotX(endX, endY, angle), y+rotY(endX, endY, angle));
    }
  }
}


//the ship
void drawShip(float x, float y, float angle) {
  strokeWeight(2f);
  stroke(0, 0, 0, 128);
  fill(118, 125, 164);


  quad(x+rotX(7, 12, angle), y+rotY(7, 12, angle), x+rotX(19, 12, angle), y+rotY(19, 12, angle), x+rotX(19, 39, angle), y+rotY(19, 39, angle), x+rotX(7, 39, angle), y+rotY(7, 39, angle)); //rThruster
  quad(x+rotX(-19, 12, angle), y+rotY(-19, 12, angle), x+rotX(-7, 12, angle), y+rotY(-7, 12, angle), x+rotX(-7, 39, angle), y+rotY(-7, 39, angle), x+rotX(-19, 39, angle), y+rotY(-19, 39, angle)); //lThruster
  fill(128, 138, 194);
  quad(x+rotX(-12, 0, angle), y+rotY(-12, 0, angle), x+rotX(-12, 34, angle), y+rotY(-12, 34, angle), x+rotX(12, 34, angle), y+rotY(12, 34, angle), x+rotX(12, 0, angle), y+rotY(12, 0, angle)); //backBod
  fill(108, 105, 134);
  quad(x+rotX(-7, -12, angle), y+rotY(-7, -12, angle), x+rotX(7, -12, angle), y+rotY(7, -12, angle), x+rotX(7, 12, angle), y+rotY(7, 12, angle), x+rotX(-7, 12, angle), y+rotY(-7, 12, angle)); //frontBod
  fill(94, 94, 158);
  quad(x+rotX(-7, -12, angle), y+rotY(-7, -12, angle), x+rotX(7, -12, angle), y+rotY(7, -12, angle), x+rotX(2, -20, angle), y+rotY(2, -20, angle), x+rotX(-2, -20, angle), y+rotY(-2, -20, angle)); //cone
  fill(0, 120, 160);
  quad(x+rotX(-12, 0, angle), y+rotY(-12, 0, angle), x+rotX(-43, 36, angle), y+rotY(-43, 36, angle), x+rotX(-16, 34, angle), y+rotY(-16, 34, angle), x+rotX(-9, 12, angle), y+rotY(-9, 12, angle)); //lWing
  quad(x+rotX(12, 0, angle), y+rotY(12, 0, angle), x+rotX(43, 36, angle), y+rotY(43, 36, angle), x+rotX(16, 34, angle), y+rotY(16, 34, angle), x+rotX(9, 12, angle), y+rotY(9, 12, angle)); //rWing
  quad(x+rotX(-16, 12, angle), y+rotY(-16, 12, angle), x+rotX(-12, 12, angle), y+rotY(-12, 12, angle), x+rotX(-12, 22, angle), y+rotY(-12, 22, angle), x+rotX(-16, 22, angle), y+rotY(-16, 22, angle)); //lDecal
  quad(x+rotX(16, 12, angle), y+rotY(16, 12, angle), x+rotX(12, 12, angle), y+rotY(12, 12, angle), x+rotX(12, 22, angle), y+rotY(12, 22, angle), x+rotX(16, 22, angle), y+rotY(16, 22, angle)); //rDecal
  stroke(200, 255, 255, 128);
  fill(0, 255, 255);
  quad(x+rotX(-2, -7, angle), y+rotY(-2, -7, angle), x+rotX(2, -7, angle), y+rotY(2, -7, angle), x+rotX(2, 29, angle), y+rotY(2, 29, angle), x+rotX(-2, 29, angle), y+rotY(-2, 29, angle)); //Power
}

//rotate a point around 0,0 by angle
float rotX(float x, float y, float angle) {
  return(x*sin(angle)-y*cos(angle));
}
float rotY(float x, float y, float angle) {
  return(y*sin(angle)+x*cos(angle));
}


//draws the base of the tentacle by setting a bunch of global variables then starting a recursive method
void drawTentacle(float x, float y, float size, float _len, float _angle, float _bend, float _wiggles, float _wiggleSize, float _wiggleStart, float _curl, color _startColor, color _endColor) {
  len = _len;
  taper = size/len;
  tentangle = _angle;
  bend = PI*_bend;
  wiggles = _wiggles;
  wiggleSize = PI*_wiggleSize;
  wiggleStart = _wiggleStart%TWO_PI;
  curl = PI*_curl;
  startColor =  _startColor;
  endColor= _endColor;
  segment=0;
  drawTentacle2(x, y, size);
}


//recursive method that makes a tentacle draw itself and all following segments
void drawTentacle2(float x, float y, float size) {


  //saves the size and color of the tentacle, to draw them at the end
  float oldsize = size;
  color myColor = lerpColor(startColor, endColor, segment/len);

  noStroke(); //draw the "outline" first
  //fill(myColor*0.4f);
  fill(0);
  ellipse(x, y, oldsize+3, oldsize+3);


  //prepare the next tentacle before drawing self
  if (segment < len && size>5) { //check if theres room for more first though
    float angleOff = sin(segment/len*(PI*wiggles)+wiggleStart)*pow(segment/len, 1.1f)*wiggleSize ; //calculate angleoffset from wigglign
    tentangle+=(bend/len);//add the turn rate, as a ratio of the tentacle's length
    tentangle += (curl/len)*(pow(max(0, segment/len), 2.5f)*4); //add the curl rate, which is strong but comes in later

    size-=taper; //decrease size of the next segment
    segment++; //keep track of semgents created
    drawTentacle2(x+cos(tentangle+angleOff)*min(10, size/3), y+sin(tentangle+angleOff)*min(10, size/3), size); //draw its child before itself.
  }


  //draw the circle at the end, thus drawing all its children On Top.


  fill(myColor);
  ellipse(x, y, oldsize, oldsize);
}


//gets angle between two points
float angleToPoint(float x1, float y1, float x2, float y2) {
  float dx = x2-x1;
  float dy = y2-y1;
  float angle = atan2(dy, dx);
  if (angle<0) {
    angle=abs(angle);
  }
  //else{angle = TWO_PI-angle;}
  return angle;
}

//gets the angle from the space horror to the ship
float horrorToShip() {
  return angleToPoint(300+parx(35f), 140, shipX, shipY);
}

//the lerp function wasnt covered in the textbooks, so I figured its not allowed. Its a simple function though so I made one of my own.
float myLerp(float tempMin, float tempMax, float rat) {
  rat = max(rat, 0);
  rat = min(rat, 1);
  return (tempMax-tempMin)*rat+tempMin;
}

float semiSeed(float val) { //gets a semi-random number using val as a seed
  return ((PI*val)%0.2*10f)-1;
}

//takes in a value, throws it on a sine wave with a length and height. 
float sinscale(float val, float wavelen, float waveheight) {
  return sin(val*(PI/wavelen))*waveheight;
}
float sinscale(float wavelen, float waveheight) {
  return sin(frameCount*(PI/wavelen))*waveheight;
}

//X Parallax based on mouse
float parx(float val) {
  return (mouseX/float(width)*val)-(val/2f);
}