/* 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;
}
}
}