> show canvas only <


/* built with Studio Sketchpad: 
 *   https://sketchpad.cc
 * 
 * observe the evolution of this sketch: 
 *   https://studio.sketchpad.cc/sp/pad/view/ro.sm2b4b9bLse/rev.611
 * 
 * authors: 
 *   Jay
 *   

 * license (unless otherwise specified): 
 *   creative commons attribution-share alike 3.0 license.
 *   https://creativecommons.org/licenses/by-sa/3.0/ 
 */ 



// PLANETS IN THE GALACTIC ELECTRON WIND, ON THE BANKS OF THE PHOTON STREAM 

/* Multiple mutual attractors with wind & water
 - Demonstrates gravitational pull amongst a group of moving objects (black vectors)
 - Demonstrates adding 'wind' and additional forces to gravitational vectors (gold vector)
 - Demonstrates drag of a viscous fluid (green vector), and a current in the 'fluid' (blue vector)
 - (velocity vectors are red).
 
 This is a mash-up of classes created by Schiffman!
 - Daniel Shiffman <http://www.shiffman.net>
 
 
 IF you want to think of these as planets, remember that the rendered object size is
 really a visual-representation of mass, and that the actual planet size would be
 invisibly small at a solar-system scale.
 
 Note that with just wind, even though the main attractor is in the 'water', it
 still is getting wind-driven velocity added to it slowly over time due to the
 planets acting like 'sails', catching the wind and getting their positions shifted
 in the windward direction, thus exerting a net pull on the main attractor in the
 same direction. (Thus the reset to zero of the main attractor's velocity was added when
 it is dragged around, since the intent is to move it back into the middle. If its
 velocity is not reset, it will only continue to grow higher, since there is no
 counter-acting force (except for a possible 'water-current'). 
 
 Adding a 'water-current' allows a longer-lasting 'balance' to be achieved,
 at least until the main attractor gets pulled out of the stream.
 
 
 G ---> universal gravitational constant
 m1 --> mass of object #1
 m2 --> mass of object #2
 d ---> distance between objects
 F = (G*m1*m2)/(d*d)
 Force = Mass * Acceleration
 Acceleration = Force / Mass
 Viscous Force = -c * (Velocity) 
 
 */

/* Instructions:
 LEFT Mouse click and drag main attractive body to move throughout space
 LEFT Mouse click on empty space sets new wind vector based on relative mouse position.
 LEFT Mouse click in 'water' sets the 'water' current direction and magnetude.
 RIGHT Mouse click resets all bodies to random starting locations
 ANY KEY press turns cycles through vector display modes.
 
 */

/*
TODO:
 - add water: DONE
 - add current to water: DONE
 - display water current vector (in blue): DONE
 - add wind: DONE
 - display wind vector (in gold): DONE
 - make wind in-effective if object is in water: DONE
 - add main movable attractor: DONE   
 - make click on empty space set new wind vector: DONE
 - make click on main attractor switch to move-mode: DONE
 - make right-click cause reset to defaults: DONE
 - add toggle-through various vector-display modes: DONE
 - ? could PID the position of the main attractor to keep it in or near the water zone,
     using the wind to keep it in the water, and the current to keep it generally on-screen.
 - ? could create a visualization that uses the vector magnitudes and colors to make a 
     psychedelic pattern on screen.
 */

//NOTE: original non-workiness was due to booleans and methods in planet
// class being named the same, so Sketchpad was evaluating a boolean to the
// method I think, as opposed to desktop Processing.


int MAX = 7; // 17 is fine on desktop if no vectors are drawn
Thing[] t = new Thing[MAX]; 
// boolean showVectors = true;
float liq_height;
float liq_start;
PVector defaultWind = new PVector(0.002, 0.0);
PVector defaultCurrent = new PVector(-0.0011, 0.0);
PVector wind;
PVector current;
int vectorDisplayMode = 0   ; // 0 = off, 1 = ALL, 2 = wind & water-current, 3 = water viscous drag, 4 = velocity, 5 = NA, 6 = NA
float c = -0.0004; // Drag coefficient
color c1 = color(32, 160, 255, 64); // water, blue
color c2 = color(245, 137, 74, 200); // main attractor, normal, red-orange
color c3 = color(255, 233, 121); // main attractor, rollover, yellow
color c4 = color(255, 144, 8); // main attractor, dragging, orange


void setup() {
  size(800, 500);
  smooth();
  reset();
  // Add water drag force
  liq_height = 100; // Liquid height
  liq_start =  height/2 - liq_height/2; // Liquid location
}

void draw() {
  background(255);



  for (int i = 0; i < t.length; i++) {          // For every Thing t[i]
    for (int j = 0; j < t.length; j++) {        // For every Thing t[j]
      if (i != j) {                             // Make sure we are not calculating gravtional pull on oneself
        PVector f = t[i].calcGravForce(t[j]);   // A 'kind' of gravity that looks good in a limited space.
        stroke(0);
        t[i].applyForce(f, 1);                     // Add the force to the object to affect its acceleration

        // Test if thing intersects liquid 'stream', infinite in +x/-x directions.
        if ((t[i].getLoc().y > liq_start) && (t[i].getLoc().y < liq_start + liq_height)) {
          // Apply the water current force..
          stroke(25, 25, 232); // set blue color for display of water current vectors
          t[i].applyForce(current, 2);

          // Drag is calculated as force vector in the negative direction of velocity
          PVector thingVel = t[i].getVel();              // Velocity of our thing
          PVector force = PVector.mult(thingVel, c);   // Following the formula above
          stroke(25, 232, 25); // set green color for display of drag vectors
          t[i].applyForce(force, 3);                        // Adding the force to our object, which will ultimately affect its acceleration
        } 
        else { // apply wind force instead...
          stroke(204, 102, 0); // set gold color for display of wind vectors
          t[i].applyForce(wind, 2);
        }
      }
    }

    t[i].go();    // Implement the rest of the object's functionality
  }


  // draw a representation of the wind vector at the top...
  stroke(204, 102, 0, 192);
  noFill();
  rectMode(CORNER);
  rect(width/2-20, 40, 40, 40);
  fill(100, 64);
  stroke(204, 102, 0); // set gold color
  drawVector(wind, new PVector(width/2, 60), 3000);
  stroke(0);



  // draw a representation of the water current vector on the left side...
  stroke(0, 0, 255, 192);
  noFill();
  rectMode(CORNER);
  rect(40, height/2-20, 40, 40);
  fill(100, 64);
  stroke(0, 0, 200); // set blue color
  drawVector(current, new PVector(60, height/2), 3000);
  stroke(0);


  // Draw the "liquid zone"
  noStroke();
  fill(c1);
  rectMode(CORNER);
  rect(0, liq_start, width, liq_height);
  
  t[0].rollover(mouseX, mouseY);
  
}

void reset() {
  PVector ac = new PVector(0.0, 0.0);
  PVector ve = new PVector(0.0, 0.0);  
  wind = defaultWind.get();
  current = defaultCurrent.get();

  // Make 1st Thing the Main Attractor
  t[0] = new Thing(ac, ve, new PVector(width/2, height/2), 650);
  t[0].isMain = true;
  t[0].dragable = true;

  // Some random bodies
  for (int i = 1; i < t.length-1; i++) {
    PVector lo = new PVector(random(width), random(height));
    t[i] = new Thing(ac, ve, lo, random(2, 4));
  }
  // Make one 'gas giant'
  PVector lo = new PVector(random(width), random(height));
  t[t.length-1] = new Thing(ac, ve, lo, random(12, 16));
}

void mousePressed() {
  if (mouseButton == RIGHT) {
    reset();
  }
  if (mouseButton == LEFT) {
    if (t[0].mRollover) { 
      t[0].clicked(mouseX, mouseY);
    } 
    else if ((mouseY > liq_start) && (mouseY < liq_start + liq_height)) {
      setCurrentVector(mouseX, mouseY);
    } 
    else {
      setWindVector(mouseX, mouseY);
    }
  }
}

void keyPressed() {
  //showVectors = !showVectors;
  if (vectorDisplayMode < 4) vectorDisplayMode += 1;
  else vectorDisplayMode = 0;
}

void mouseReleased() {
  t[0].stopDragging();
}

// wind interactive control calc
void setWindVector(int mx, int my) {
  PVector m = new PVector(mx, my);
  PVector w = new PVector(width/2, 60);
  PVector diff = PVector.sub(m, w);
  diff.mult(0.03);
  float magnetude = diff.mag();
  diff.normalize();
  diff.mult(magnetude*0.01);
  wind = diff.get();
  wind.limit(0.02);
}

// water-current interactive control calc
void setCurrentVector(int mx, int my) {
  PVector m = new PVector(mx, my);
  PVector c = new PVector(60, height/2);
  PVector diff = PVector.sub(m, c);
  diff.mult(0.03);
  float magnetude = diff.mag();
  diff.normalize();
  diff.mult(magnetude*0.01);
  current = diff.get();
  current.limit(0.02);
}

// Renders a vector object 'v' as an arrow and a location 'loc'
void drawVector(PVector v, PVector loc, float scayl) {
  if (v.mag() > 0.0) {
    pushMatrix();
    float arrowsize = 4;
    // Translate to location to render vector
    translate(loc.x, loc.y);
    //stroke(0); // let calling line set vector color.
    // Call vector heading function to get direction (note that pointing up is a heading of 0) and rotate
    rotate(v.heading2D());
    // Calculate length of vector & scale it to be bigger or smaller if necessary
    float len = v.mag()*scayl;
    // Draw three lines to make an arrow (draw pointing up since we've rotate to the proper direction
    line(0, 0, len, 0);
    line(len, 0, len-arrowsize, +arrowsize/2);
    line(len, 0, len-arrowsize, -arrowsize/2);
    popMatrix();
  }
}




// A class to describe a thing in our world, has vectors for location, velocity, and acceleration
// Also includes scalar values for mass, maximum velocity, and elasticity
// original by Daniel Shiffman <http://www.shiffman.net>
// Jay: added mouse-dragability, 
// Jay: fixed applyForce method to not modify global force input, important for wind display

class Thing {
  PVector loc;
  PVector vel;
  PVector acc;
  float mass;
  float max_vel;
  float bounce = 1.0f; // How "elastic" is the object
  float G;             // Universal gravitational constant

  boolean isMain  = false;  // Is it the main attractor?
  boolean dragable = false; // Is it draggable?
  boolean dragging = false; // Is the object being dragged?
  boolean mRollover = false; // Is the mouse over the ellipse?
  PVector mDrag;  // holds the offset for when object is clicked on


  Thing(PVector a, PVector v, PVector l, float m_) {
    acc = a.get();
    vel = v.get();
    loc = l.get();
    mass = m_;
    max_vel = 5.0;
    G = 0.2;
    mDrag = new PVector(0.0, 0.0);
  }

  PVector getLoc() {
    return loc;
  }

  PVector getVel() {
    return vel;
  }
  float getMass() {
    return mass;
  }

  PVector calcGravForce(Thing t) {
    PVector dir = PVector.sub(t.getLoc(), loc);        // Calculate direction of force
    float d = dir.mag();                               // Distance between objects
    d = constrain(d, 35.0, 38.0);                      // Limiting the distance to eliminate "extreme" results for very close or very far objects
    // original: 20.0, 50.0
    dir.normalize();                                   // Normalize vector (distance doesn't matter here, we just want this vector for direction)
    float force = (G * mass * t.getMass()) / (d * d);  // Calculate gravitional force magnitude
    dir.mult(force);                                   // Get force vector --> magnitude * direction
    return dir;
  }

  void applyForce(PVector force, int type) { // where type is the type of vector to draw
    PVector f;
    f = force.get();
    f.div(mass);
    acc.add(f);
    //if (showVectors) {
    switch(vectorDisplayMode) {
    case 0: 
      // NO vectors drawn
      break;
    case 1: //ALL vectors drawn
      drawVector(f, loc, 5000);
      break;
    case 2: //wind & water-current vectors only
      if (type == 2) drawVector(f, loc, 5000);
      break;
    case 3: //drag vectors only
      if (type == 3) drawVector(f, loc, 5000);
      break;
    case 4: //velocity vectors only
      if (type == 4) drawVector(f, loc, 5000);
      break;
    case 5: //NA
      //if (type == 5 ) drawVector(f, loc, 5000);
      break;
    case 6: //NA
      //drawVector(f, loc, 5000);
    }


    //}
  }

  // Main method to operate object
  void go() {
    update();
    render();
    if (dragable) {
      drag();
    }
  }

  // Method to update location
  void update() {
    vel.add(acc);
    vel.limit(max_vel);
    loc.add(vel);
    acc.mult(0);
  }

  // Method to display
  void render() {
    ellipseMode(CENTER);
    
    if (isMain) {
      if (dragging) fill (c4); // orange
      else if (mRollover) fill(c3); // yellow
      else fill(c2); // red-orange
    } 
    else {
      fill(132, 80);
    }
    if (isMain) { // don't render main attractor at full 'mass' size...
      noStroke();
      ellipse(loc.x, loc.y, 0.2 * mass, 0.2 * mass);
    } 
    else {
      stroke(64);
      ellipse(loc.x, loc.y, 2*mass, 2  *mass);
    }
    if ((vectorDisplayMode == 1) || (vectorDisplayMode == 4)) { //draw velocity vectors (in red)
      stroke(232, 0, 0);
      drawVector(vel, loc, 20);
    }
  }


  // The methods below are for mouse interaction with main attractor.
  void clicked(int mx, int my) {
    float d = dist(mx, my, loc.x, loc.y);
    if (d < mass) {
      dragging = true;
      mDrag.x = loc.x-mx;
      mDrag.y = loc.y-my;
    }
  }

  void rollover(int m_x, int m_y) {
    float d = dist(m_x, m_y, loc.x, loc.y);
    
    if (d < (0.1 * mass)) {
      mRollover  = true;
    } 
    else {
      mRollover = false;
    }
  }

  void stopDragging() {
    dragging = false;
    t[0].vel.mult(0);
  }

  void drag() {
    if (dragging) {
      loc.x = mouseX + mDrag .x;
      loc.y = mouseY + mDrag .y;
    }
  }
  
}