//// Projectile_1:  angle-select via circle; velocity & angle determined by key duration.
//// Angle selector.  (Also use key duration to select values.)
//// Click to locate selector at (x,y) 

String title="Projectile #1";
String author="bam@professorBAM.com";
String version=  "9817a";
String sub1="Click near pointer to change angle";
String sub2="\n(then click elsewhere.)";
String subtitle=sub1+sub2;
String news="";
String helpText="Press 'r' key to relocate the angle-selector";
String blank=" ", tab="    ", nl="\n", pi="π";
color bgcolor=  color(200,225,250);
int nextX=10, nextY=100, nextH=15, nextCounter=0;
int fireX=width,fireY=height;
//
int durationX=300, durationY=470, durationDX=2, durationDY=12;
float rate=60;
int n=0;
int press=0, release=0, duration=0;
char whatkey=' ';

float velocity=50, angle=PI/4, gravity=9.81;
float vx,vy;
float previous[];
int np=0;

// Circle to specify angle.
float ax=50, ay=500, ar=50;        // Initial position
Angle a= new Angle( ax, ay, ar );
boolean relocate=false, initial=false, pause=false;

Bullet b= new Bullet( ax,ay, 0, 0 );

void setup() {
  size(800,600);
  frameRate(rate);
  //--  helpText=  getHelp();
  reset();
  previous= new float[50];
  np=0;
  for (float p : previous) {p=0;}
  previous[0]=50;
  previous[1]=200;
  previous[2]=200;
  previous[3]=300;
  previous[4]=400;
  previous[5]=500;
  previous[6]=600;
  previous[7]=700;
}
void reset() {
  vx=  velocity * (float) Math.cos(a.angle);
  vy=  velocity * (float) Math.sin(a.angle);
  if (b.distance > 0 && np<previous.length) {
    println( "SAVING ", b.distance );
    previous[np++]=  b.distance; 
  }
  b= new Bullet( a.cx,a.cy, vx,vy );
}

void draw() {
  background( bgcolor );
  fill(0);
  textSize(24);
  text( title, width/3, 20 );  
  textSize(12);
  text( subtitle, width/3, 40 );  
  fill(255,0,255);
  text( news+".", 10, 66 );
  fill(0,100,0);
  showHelp( width*2/3, 66 );
  fill(100);
  textSize(10);
  text( author, 10, height-20 );
  fill(150);
  text( "©2019  "+version, 10, height-10 );
  

  fill(0,0,255);
  data( 10, 110 );
  a.show();
  
  //// Projectile in flight.
  b.show();
  if (! pause) b.move();
  if (duration>0) text( duration, durationX+25, durationY );
}
void data( int x, int y ) {
  nextX=x;
  nextY=y;
  nextCounter=0;
  next( "Velocity = " +velocity );
  next( "Angle = " +a.angle);
  next( "  = " +a.pimul+ " π" );
  next( "  ~ " +a.degrees+ "º" );
  //
  next( "" );
  next( "position:  (" +(int)b.x+ ", " +(int)b.y+ ")" );
  next( "velocity:  " +(int)b.dx+ ", " +(int)b.dy     );
  next( "time:      " +(int)(1000*b.time)+ " ms");
  // Display old markers.
  fill( 150,0,150, 50 );
  if (previous[0]>0) {
    for (int j=0; j<previous.length; ++j) {
      if (previous[j]>0) { 
        fill(150,0,150 ); 
        b.oldmarker( (int) previous[j] ); 
      };
    }
  }
}
void next( String s ) {
  text( s, nextX, nextY + nextH*nextCounter++ );    // Next line of text
}
void nextReset() {
  nextCounter=0;
}
void showHelp( int x, int y ) {
  nextX=x;
  nextY=y;
  nextCounter=0; 
  next( "Press 'r' key to relocate selector" );
  next( "Press 'i' key to specify initial velocity" );
  next( "    Holding 'a' key also sets angle" );
  next( "    Holding 'v' key also sets velocity" );
  next( "    Holding 'p' key pauses the action" );
  next( "q to quit" );
  fill(255,0,0);
  next( "Press 'f' key to FIRE the projectile\n  (or click HERE)" );
  //Save coordinaters of "FIRE"
  fireX=  nextX+112;
  fireY=  nextY+8 + nextH*nextCounter++;
  fill( 255,220,220, 150 );
  ellipse( fireX, fireY, 50,50 ); 
}



void keyPressed() {
  if (key == 'p')   { pause=true; return; }
  pause=false;
  if (press==0) {
    news=  ""+key;
    press=  millis();     // Save the starting time for keypress,
    durationX=300;
    whatkey=key;
    text( ""+whatkey, durationX, durationY );
    news=""+whatkey;
  }
  news+=  ""+whatkey;
  point( durationX+20, durationY );        // Keep adding dots, while key remains pressed.
  durationX+= durationDX; 
  ++n;
}
void keyReleased() {
  if (key == CODED) { news=""; return; }  // Ignore control keys.
  if (key == ' ')   { news=""; return; }
  if (key == 'p')   { pause=false; return; }
  if (key == 'q')   { exit(); }
  release=  millis();
  duration=  release - press;
  news=  whatkey+ ":  " +duration+ " ms.";
  //--  news+= "\n    (" +press+ "-" +release+ ")";
  if (n>1) news+= "        " +n+ " frames";
  press=release=0;
  n=0;
  //
  if (key == 'f') { fire(); }
  else if (key == 'a') angle=  (duration/100) * PI * 2;
  else if (key == 'v') velocity=  duration / 100;
  else if (key == 'g') gravity=  duration / 100;
  else if (key=='+' || key=='=') a.plus();
  else if (key=='-' || key=='_') a.minus();
  //
  else if (key == 'r') { 
    // Relocate the selector
    subtitle=  "Click to relocate the selector";
    news=  "Click to relocate the selector";
    relocate=true;
    a.visible=false; 
  }
  else if (key == 'i') { 
    // Initial velocity
    subtitle=  "Click along vector line to indicate initial velocity";
    news=  "Click to along vector line to indicate initial velocity";
    initial=true;
    a.vector=true;
  }
  else  if (key == 's') { b.stop(); return; }
  else if (key == 'r') {
    // Relocate the selector
    relocate=true;
    a.visible=false;
    return;
  }
  else if (key == 'i') {
    // Indicate the velocity (along vector line)
    initial=true;
    a.vector=true;
    return;
  }
  //
  else news+=  "\nNO SUCH KEY:  " +whatkey;
}
void fire() {
    background(255,0,0);
    reset();
    b.time=0;
}

void mousePressed() {
  pause=false;
  if ( a.near(mouseX,mouseY) ) {
    // click is within circle
    a.select(mouseX,mouseY);
    return;
  }
  if ( dist(mouseX,mouseY, fireX,fireY) <  30) {
    // click is near FIRE
    fire();
    return;
  }
  else if (relocate) {
    ax=mouseX;        // New location for selector
    ay=mouseY;
    float tmp=  a.angle;
    a= new Angle( ax, ay, ar );
    a.newAngle( tmp );
    a.visible=true;
    relocate=false;
    subtitle=sub1+sub2;
  }
  else if (initial) {
    ax=mouseX;        // New location for selector
    ay=mouseY;
    velocity=  dist( a.cx,a.cy, mouseX, mouseY ) / 3;
    a.vector=false;
    initial=false;
    news= "initial velocity:  " + velocity;
    subtitle=sub1+sub2;
  }
  else {
    a.visible=false;
  }
}



class Angle {
  float angle=PI/4, pimul=0.25, degrees=45;
  float cx=50, cy=400, cr=40;         // Circle
  float px=50, py=400;                // Pointer
  int crust=3, thick=12;
  boolean visible=true, vector=false;
  //// CONSTRUCTOR(S) ////
  Angle(float cx, float cy, float cr) {
    this.cx=cx;
    this.cy=cy;
    this.cr=cr;
  }
  //// METHODS ////
  void show() {
    if (visible) {
      circle();
      fill(0);
      text( "Click on circle\nto change angle", cx-cr+6, cy-cr-36);
    }
    pointer(angle);
 }
 void circle(  ) {
    // Draw a circle to specify angle.
    strokeWeight(crust);
    fill( 200,255,255 );
    stroke(0,0,255);
    ellipse( cx, cy, cr*2, cr*2 );
    strokeWeight(1);
    line( cx,cy, cx+cr, cy );
    line( cx,cy, cx-cr, cy );
    line( cx,cy, cx, cy+cr );
    line( cx,cy, cx, cy-cr );
  }
  void pointer( float angle ) {
    float px,py;
    px=  cx + cr*cos(angle);
    py=  cy - cr*sin(angle);
    strokeWeight(thick);
    stroke(0);
    line( cx,cy, px, py );
    fill(255);
    noStroke();
    strokeWeight(1);
    int tip=  int(thick*3/4);        // white circle at tip
    ellipse( px,py, tip,tip );
    fill(0);
    if (visible) text( degrees+ "º", px+20,py );
    text( degrees+ "º", cx-20,cy+cr+20 );
    // Horizon.
    stroke( 0,150,0, 100 );
    line( cx, cy, width-20, cy ); 
    // Vector line
    if (vector) {
      float vx,vy;
      vx=  cx + width*cos(angle);
      vy=  cy - width*sin(angle);
      stroke( 255,0,0 );
      fill( 255,0,0 );
      line( cx, cy, vx, vy ); 
    }
    fill(0);
  }
  boolean near( float x, float y ) {
    return ( dist( x,y, cx,cy) < cr+20 );
    // true if x,y is within (or near) circle
  }
  void select( float x, float y ) {
    // Set new angle
    newAngle( getAngle( x, y ) );
  }
  void newAngle( float radians ) {
    angle=  radians;
    degrees=  angle * 180 / PI;
    pimul=  (angle/PI)%2;
    visible=true;
  } 
  float getAngle( float x, float y ) {
     // Returns angle above horizontal (-π/2 to 3π/2)
     float dx, dy;
     dx=  x-cx;
     dy=  y-cy;
     if (x>=cx) return -atan( dy / dx );
     else return PI - atan( dy / dx );
  }
  // + or - bumps angle ny +/- 1 degree
  void plus() { setAngle( degrees+1 ); }
  void minus() { setAngle( degrees-1 ); }
  void setAngle( float d ) {
    degrees=  (int) d;
    angle=  degrees * PI / 180;
    pimul=  (angle/PI)%2;
    visible=true;
  }
}

class Bullet {
  float x=0,y=0,dx=0,dy=0;  // Position & velocity components.
  float angle=  PI/4;
  float time=0;             // Time
  float mass=1;             // (???)
  float gravity=  9.81;
  float radius=10;
  float horizon, distance=0;
  //// CONSTRUCTOR(S) ////
  Bullet( float x, float y, float dx, float dy ) {
    this.x=x;    this.y=y;
    this.dx=dx;  this.dy=dy;
    horizon=  y;
  }
  //// METHODS ////
  void move() {
    if (x>width || y>height+20) stop();
    if (x<0) stop();
    if (time<0) return;
    ++time;
    x+=  dx/rate;
    y-=  dy/rate;
    dy-=  gravity/rate;
    if (y>horizon) {
      news= "Bullet drops below horizon!";
    }
    if (y>height+20) {
      news= "";
    }   
  }
  void stop() {
    time=  time>0 ? -time : time;
  }
  void show() {
    if (time >= 0) {
      fill(0);
      if (y>horizon) { 
        fill( 20,20,20, 100 ); 
        splash( x,horizon );  
        if (distance<=0) distance=x;
      }
      if (y<height) ellipse( x,y, 2*radius,2*radius );
    }
    // Show distance (if nonzero).
    if (distance>0) { marker( distance); }
  }
  void splash( float xx, float yy ) {
      line( xx,yy, xx-60,yy-40 );
      line( xx,yy, xx-20,yy-60 );
      line( xx,yy, xx+20,yy-60 );
      line( xx,yy, xx+60,yy-40 );
  }
  void marker( float x ) {
    fill(255,0,255);
    rect(x-2,horizon-4, 4,8 ); 
    text( x, x,horizon );  
  }
  void oldmarker( float x ) {
    fill(150,0,150, 50);
    rect(x-2,horizon-4, 4,8 ); 
    text( (int) x, x-2,horizon+14 );  
  }
}
