Bike bb;

import processing.candy.*;
import processing.xml.*;
SVG redBike;
SVG frontTire;

boolean introMode = true;
boolean instructionsMode = false;
boolean drawMode = false;
boolean playMode = false;
boolean winMode = false;
boolean crash;
float[] trackShape;
float segLength = 40;
int trackScroll = 0;
int travelDist = 0;
PImage dirt;
PImage title;
PImage instruct;
PFont arial;

float zoom = 1;
float wheelRadius = 7;

int airPoints;

void setup()
{
   size(600, 600);
   smooth();
   stroke(0);
   strokeWeight(1);
   frameRate(30);
   fill(90, 60, 0);
   
   //Assign original shape to track
   trackShape = new float [width*2];
   for(trackScroll = 0; trackScroll < trackShape.length; trackScroll++)
  {
    trackShape[trackScroll] = height/2;
    //trackShape[trackScroll] = (sin(trackScroll/40.0)*20)+height/2;
  }
  
   redBike = new SVG(this, "bike_body.svg");
   frontTire = new SVG (this, "bike_tire.svg");
   dirt = loadImage("dirt.png");
   title = loadImage("title.jpg");
   arial = loadFont ("Arial.vlw");
   textFont(arial);
}

void draw()
{
  
//////////////////// DRAW TRACK MODE ////////////////////
  if(introMode)
  {
    image (title, 0, 0);
    if((keyPressed) && (key == ENTER))
    {
    title = loadImage("instruct.jpg");
    }
  }

  if (drawMode)
  {
    stroke(0);
    zoom = 1;
    drawTrack();
  }
  
//////////////////// PLAY MODE ////////////////////
  if (playMode)
  {
    background(100, 60, 20);
    if(zoom < 4)
      zoom += 0.1;
    else
      zoom = 4;
    
//////////////////// Zoom with focus on bike's position ////////////////////
 for(int i = int(-bb.frontX*(zoom-1)); i < width + bb.frontX; i+= 100)
  {
    for(int j = int(-bb.frontY*(zoom-1)); j < height+ bb.frontY; j += 100)
    image(dirt, i, j);
  }
  
    fill (139, 217, 255);
    noStroke();
    pushMatrix();
    beginShape();
    vertex(0, 0);
    translate(-bb.frontX*(zoom-1), -bb.frontY*(zoom-1)-((wheelRadius-1)*(zoom-1)));
    for(int i = 0; i < width-1; i++)
    {
      vertex(i*zoom, trackShape[i]*zoom);
    }
    vertex(width*zoom*2, height/2*zoom);
    vertex(width*zoom*2, 0);
    vertex(0, 0);
    endShape();
    popMatrix();
  
  if(bb.backX < width)
  {  bb.calculate();
    bb.display();
  }
  
  fill(255);
  if(crash)
    fill(255, 0, 0);

  textFont(arial);
  textAlign(RIGHT);
  pushMatrix();
  scale(0.5);
  text(nf(airPoints,4) + "PTS", width*2-20, 40);
  popMatrix();
  }
  
  
  if (winMode)
  {
    winscreen();
  }
  
  if((keyPressed) && (key == BACKSPACE))
  {
    drawMode = true;
    playMode = false;
    winMode = false;
    introMode = false;
  }
}

class Bike
{
  float frontX, frontY;
  float prevX, prevY;
  float xSpeed, ySpeed;
  float xAccel, yAccel;
  float groundFront, groundBack, clear;
  
  float backX;
  float backY;
  float angle1 = 0.0;
  float dx, dy;
  boolean airtime = false;
  
  Bike(float ex, float why, float exSpeed, float whySpeed)
  {
    frontX = ex;
    frontY = why;
    xSpeed = exSpeed;
    ySpeed = whySpeed;
    
    xAccel = 0.0;
    yAccel = 0.2;
    
    backX = frontX-1;
    backY = frontY+50;
    
    airPoints = 0;
    
    crash = false;
  }
  
//////////////////// BEGIN CALCULATE //////////////////// 
  void calculate()
  {
    if((frontX < width+150-xSpeed-wheelRadius)&&(frontX > 1))
    {
      if((keyPressed) && (key == CODED))
      {
        if (keyCode == RIGHT)
        xAccel = 0.1;
        else if (keyCode == LEFT)
        {
          if (xSpeed <= 0)
          {
             xSpeed = 0;
             xAccel = 0;
          }
          else
            xAccel = -0.1;
        }
      }
      else
        xAccel = 0;
        
    groundFront = (trackShape[int(frontX)]);
    clear = trackShape[int(frontX+(wheelRadius/zoom))];
    
    if((frontY < clear-ySpeed)&&(crash == false))  //if track in front is clear... 
    {
      //and if ground is touched, then stop gravity, and accelerate
      if(frontY >= groundFront-wheelRadius-1)
      {
        //Limit xSpeed to 3 pixels per frame
        if(xSpeed > 2)
          xSpeed = 2;
        else
          xSpeed += xAccel;
          
        frontY = groundFront - wheelRadius + ySpeed+1;
        ySpeed = (frontY - backY) * (xSpeed/20);
        
        airtime = false;
      }
      else           //else, if no ground, activate gravity
      {
        ySpeed += yAccel;
        frontY += ySpeed;
        
        airPoints ++;
        
        airtime = true;
      }
    }
    else          // else (if crash)
    {
      crash = true;
      xSpeed = 0;
      xAccel = 0;
      
      airPoints = 0;
      
      if(frontY >= groundFront-wheelRadius)
      {
        frontY = groundFront-wheelRadius;
      
      }
      else           //else, if no ground, activate gravity
      {
        ySpeed += yAccel;
        frontY += ySpeed;
      }
    }
    frontX += xSpeed;
    }
        dx = frontX - backX;
        dy = frontY - backY;
        angle1 = atan2(dy, dx);
        if(!crash)
          backX = frontX - (cos(angle1) * segLength);
        else
          backX = frontX - (cos(angle1) * segLength)+ySpeed;
        
        if(backX > 0)
        {
        //And here are the two hardest lines ever
        groundBack = (trackShape[int(backX + ( ((frontX - backX) /zoom)*(zoom-1)))]-(wheelRadius));
        groundBack += ((groundBack+wheelRadius)-(frontY+wheelRadius-1))*(zoom);
        }
        else
        groundBack = trackShape[0];
        
        if (backY < groundBack)// if back wheel is above ground
        {
          if(airtime)
            backY = frontY - (sin(angle1) * segLength);
          else
            backY = frontY - (sin(angle1) * segLength)+2;
        }
        else 
        backY = groundBack+1;
        
    if(frontX > width)
    {
      winMode = true;
      playMode = false;
      
    }
    }
//////////////////// END OF CALCULATE ////////////////////

  void display(){
      frontTire.drawMode(CORNER);
    if (crash)
      frontTire.draw(frontX-20, frontY-12);
    else
    {
      frontTire.draw(frontX-10, frontY-12);
    }
      shape(backX, backY, angle1);
  }

  void shape(float segX, float segY, float segA) {
    pushMatrix();
    translate(segX, segY);
    rotate(segA);
    redBike.drawMode(CORNER);
    redBike.draw(-15, -30);
    
    popMatrix();
  }
}

void drawTrack()
{  
  float drawY = mouseY;
  float pdrawY = pmouseY;
  float xSteps, ySteps;
  float increment;
  
  background(255);
  
  if(mousePressed)
  {
    // Constrain mouseX positions to prevent array out of bounds 
    int drawX = constrain (mouseX, 0, width+15);
    int pdrawX = constrain (pmouseX, 0, width+15);

    if(drawX > pdrawX)
    {
      xSteps = drawX - pdrawX;
      ySteps = drawY - pdrawY;
      increment = (ySteps*1.0) / xSteps;
      
      for(int i = pdrawX; i <= drawX; i++)
      {
      trackShape[i] = pdrawY;
      pdrawY += increment;
      }
    }
  }

  //Display Current Draw Status
  for(int i = 0; i < width-1; i++)
  {
    line (i, trackShape[i], (i+1), trackShape[i+1]);
  }
  
  if((keyPressed) && (key == ENTER))
  {
    drawMode = false;
    playMode = true;
    
    bb = new Bike(10, trackShape[10] -wheelRadius, 0, 0);
  }
}

void winscreen()
{
  int combination = int((trackShape[0]-bb.frontY)/10)+1;
  
  fill(255, 25);
  
  pushMatrix();
  scale(0.5);
  
  textAlign(LEFT);
  text("Your score is:", 100, height/2-100);
  text(airPoints + " air points", 100, height/2-50);
  text( "X " + combination + " for climbing = ", 100, height/2);
  text(airPoints * combination + " points!", 100, height/2 + 50);
  
  popMatrix();
}

Project 3.3: