//// Billiard balls -- Elastic collisions //// h9_elastic.pde: Prof.BAM //// Physics: remove assumptions (point mass, collinear, etc.) //// 509w: move first, then check bump (separate first?) String title= "Billiard balls -- elastic collisions", subtitle= "Click table rim to shoot cueball. ", author= "Prof.BAM", filename= "h9_elastic.pde", version= "20509w", news= "(No news is good news.)", hints= "q quits; r resets; ? for help", COMMA=",", CS=", ", NL="\n", _="_"; char THETA='Θ', PHI='Φ'; color newscolor = 0; // Assume size(600,400) // int left=50, right=550, top=100, bottom=300, rim=25; int center=int((left+right)/2), middle=int((top+bottom)/2); int spot= int((left+center)/2), wide=right-left, high=bottom-top; float force=10, trivial=0.02, friction=0.01, px=0, py=0, p0=0, v=0; int score1=0, score2=0, diam=30, near=50, ballnumber=0; float framewas; boolean debug=false, bounce=false, mass=true, unrack=false, pop=false; boolean kill=false, jump=false, kp=false; int pw=36, bw=30; int cuespot, rackspot; int nb=16, ng=21, nbng=nb+ng, ndead=0, noob=0, nv=0; Ball[] b = new Ball[nbng]; Ball cue; Table t; //-- float force=10; boolean colorShow=false; void setup() { size( 800, 600 ); smooth(); framewas = frameRate; // Table. left=50; right=width-50; top=100; bottom=height-100; wide=right-left; high=bottom-top; center= (right+left)/2; middle=(top+bottom)/2; cuespot= left + wide/4; rackspot= center; t = new Table( left, top, right, bottom, rim ); // Balls. for (int i=0; i1 || debug) println( (2*loops)+" MOVES: ", b1.n, b2.n, d, bw, "====\n" ); if (loops>1) news += "**** "+ (2*loops)+ " MOVES! "+b1.n+CS+b2.n; } */ void swapv( Ball b1, Ball b2 ) { // Point-Mass: swap xx's and yy's. // v1 transfers to b2 and vice versa. float v; // temp for swapping. v = b1.xx; b1.xx = b[2].xx; b2.xx = v; v = b1.yy; b1.yy = b2.yy; b2.yy = v; // Retreat (2 paces!) b1.move(); b1.move(); b2.move(); b2.move(); } // Fell into pocket? // void fall() { for (int i=0; i0) text( s, width*3, height-12 ); if (colorShow || debug) { s=" 'c' removes bottom balls."; if (debug) s=" 'd' turns off debugging."; fill(RED); text( s, width*2/3, height-8 ); } fill(YELLOW); if (ndead>0) text( ndead+" dead.", left+100, top-10 ); if (noob>0) text( noob+" Out of Bounds.", left+200, top-10 ); text( "force: "+force, width*3/4, top-10 ); if (! mass) text( "point-mass", pw+width/2, top-10 ); if (round(frameRate) != 60) text( "frameRate: "+round(frameRate), width*3/4, bottom+15 ); if (pop) text( "pop", 10+pw/2+width/2, bottom+15 ); // VELOCITIES. // fill(RED); if (v>0) { int tx=width-200, ty=width-100, tn=width-20; textSize(16); text( " V: "+v, tx, 16 ); textSize(12); text( b[0].xx+NL+px, tx, 36 ); text( b[0].yy+NL+py, ty, 36 ); text( NL+nv, tn, 36 ); } // SCORE. // fill(BLUE); score1=score2=0; if ( ndead>0 ) { textSize(24); for (int i=1; i<7; ++i) score1 += b[i].dead ? 1 : 0; for (int i=9; i<15; ++i) score2 += b[i].dead ? 1 : 0; } textSize(24); if (score1+score2>0) text( "Score is "+score1+":"+score2, 10, 30 ); textSize(12); s= "("+round(mouseX) +", "+round(mouseY)+")"; if (debug) text( s, width/4, height-8 ); s= ""+key; if (keyPressed) s += " key pressed. "; if (keyCode>0) s += keyCode; text( s, width/2, height-10 ); if (jump) text( "JUMP", width/2-80, height-8 ); if (kill) text( "KILL", width/2-40, height-8 ); // v = abs(p0+px+py); if (v <= trivial) { //// Display shooting line, when motion stops. if (t.bumper( mouseX, mouseY) && ! b[0].dead ) { stroke( mass ? RED : YELLOW ); strokeWeight(2); line( mouseX, mouseY, b[0].x, b[0].y ); strokeWeight(1); } } // Display help while '?' key (or SHIFT) is pressed. if (key == '?') help(); } //////// OBJECT CLASSES //////// class Table { float left, top, right, bottom, rim; Table( float left, float top, float right, float bottom, float rim ) { this.left=left; this.top=top; this.right=right; this.bottom=bottom; this.rim=rim; } void show() { //// lite-green light-table, with thick brown border, on cyan backgroud. rectMode(CORNERS); fill( 100, 50, 50 ); // Brown (wood) rail. rect( left-rim, top-rim, right+rim, bottom+rim ); fill( 100, 200, 100 ); // Light-green table. rect( left, top, right, bottom ); // pockets. // fill(GRAY); ellipse( left, top, pw, pw ); ellipse( center, top, pw, pw ); ellipse( right, top, pw, pw ); ellipse( left, bottom, pw, pw ); ellipse( center, bottom, pw, pw ); ellipse( right, bottom, pw, pw ); } boolean within( float x, float y ) { //// True iff x,y inside table. if (xright) return false; if (ybottom) return false; return true; } boolean bumper( float x, float y) { //// Check if outside thabnle but inside bumper. if (within(x, y)) return false; if (xright+rim) return false; if (ybottom+rim) return false; return true; } }// class Table // class Ball { float x, y, xx, yy; // position, velocity. float xxx=friction, yyy=friction; // deceleration (friction). int w=bw; // width (diameter) color c=color(#FFFFFF), cn=0, glowColor=WHITE; int countdown=0, countmax=60; int n, xbounce=0, ybounce=0; String name="", pn, xd, yd; boolean dead=false, oob=false; // CONSTRUCTORS // Ball() { reset(); } Ball( int n, color c, color cn ) { this.n = n; this.c = c; this.cn = cn; reset(); } // Ball.reset() // void reset() { //-- x = center + int(w*n/2); //-- y = middle + int(n%2>0 ? w*n/4 : -w*n/4 ); xx=yy=0; // At rest. rack( this ); dead=oob=false; countdown=0; glowColor=WHITE; } void set( float x, float y ) { this.x=x; this.y=y; xx=yy=0; dead=oob=false; } void ranset( ) { set( random( left+bw, right-bw ), random( top+bw, bottom-bw ) ); } //// Ball.show() //// void show() { show(x, y); if (debug) where( x, y ); } void show(float x, float y) { if (--countdown>0) showglow(); noStroke(); stroke(GRAY); strokeWeight(1); fill(c); if (dead) fill(c, 127); if (dead) stroke(255); ellipse( x, y, w, w ); fill(cn); int tx = n<10 ? 4 : 8; if (n8 & n<26) { fill(#EEEEEE); // Stripes // ellipse( x-(w*4/10), y, w/5, w/2 ); ellipse( x+(w*4/10), y, w/5, w/2 ); } } String whereami() { String s=""; if (dead) s="dead."+pn+": "+whereDead(); else if (oob) s="OOB"; else s="( " + round(x) + CS +round(y) +" )"; return s; } String whereDead() { xd= x < center-wide/4 ? "left" : (x>center+wide/4) ? "right" : "center"; yd= ytrivial) { fill(0); s += NL+(int)round(xx)+ "/" +(int)round(yy); text( s, xt, yt+12 + 4*(n%2) ); } else if (xx != 0 || yy != 0) { fill(RED); // ???? THIS SHOULD NOT HAPPEN! textSize(20); text( NL + xx+ "/" +yy, xt-10, yt+12 + 4*(n%2) ); } textSize(12); } void glow( color g ) { countdown=countmax; glowColor=g; } void showglow() { if (dead) return; if (abs(xx)+abs(yy) < 0.5) return; stroke(glowColor); fill( c, 127*countdown/countmax ); ellipse(x, y, w*2, w*2); stroke(0); } //// Ball.move() //// void move() { if (dead) return; if (oob) return; if (xright || ybottom) { if (debug) println( n+": OOB", x, y, xx, yy ); if (pop) popin(); else { oob=true; popout(); } xx=yy=0; return; } if (abs(xx)0 ? xx-xxx : xx+xxx; yy = yy>0 ? yy-yyy : yy+yyy; // Reduce magnitude (friction) // Bounce. // if (bounce) { //-- println( n, x, y, xx, yy ); if (xright-bw/2) println( n+":bounce xx: ", -xx, xbounce ); if (ybottom-bw/2) println( n+":bounce yy: ", -yy, ybounce ); } if (xright-bw/2) { ++xbounce; xx = -abs(xx); x = right - bw; }; if (ybottom-bw/2) { ++ybounce; yy = -abs(yy); y = bottom - bw; }; if (xbounce>50 || ybounce>30) { println( "TOO MANY BOUNCES", xbounce, ybounce, xx, yy, " (STOP!)" ); news += NL+"TOO MANY BOUNCES: "+xbounce+_+ybounce; xx=yy=0; xbounce=ybounce=0; } // Add velocity. x += xx; y += yy; if (debug) { //// Out-of-bounds if (xright-bw/2) println( n+".x > ", right-bw/2, x ); if (ybottom-bw/2) println( n+".y > ", bottom-bw/2, y ); } } void popin() { //// Pop ball back inbounds. (no OOB) print( n, "popin", x, y ); if (xright-bw/2) x=right-3/2*bw; if (ybottom-bw/2) y=bottom-3/2*bw; oob=false; //-- println( x, y ); } void popout() { //// Force out-of-bounds if (xright-bw/2) x=right+rim+bw/2; if (ybottom-bw/2) y=top-rim-bw/2; } void jumped() { //// Force out-of-bounds if (oob) return; if (dead) return; y=top-rim-bw/2; oob=true; jump=kill=false; } //// SHOOT //// void shoot( float x2, float y2 ) { float run=x2-x, rise=y2-y, hyp=dist(x2, y2, x, y); xx = -force * run/hyp; // sine yy = -force * rise/hyp; // cosine //-- news= "SHOOT: "+force+" "+xx+","+yy; news= "SHOOT: ("+int(force)+")"; newscolor=RED; } //// Methods to detect hits. //// boolean clicked() { return hit(mouseX, mouseY); } boolean hit( float x2, float y2 ) { return hit(x2, y2, 50); } boolean hit( Ball b, float near) { return hit(b.x, b.y, near); } boolean hit( float x2, float y2, float near) { return dist(x, y, x2, y2) <= near+1; } void pocket( float x2, float y2 ) { if (hit(x2, y2, pw*2/3)) caught( x2, y2 ); } void caught( float x2, float y2 ) { if (dead) return; dead=true; xx=yy=0; x= x2 - 10 + n*2; y= y2 - 10 + n*2; pn = "" + (int)x2/100 + (int)y2/100; //-- xd= x < center-wide/4 ? "left" : (x>center+wide/4) ? "right" : "center"; //-- yd= yleft+wide/4) xnew=center; if (x>center+wide/4) xnew=right; if (y>middle) ynew=bottom; caught( xnew, ynew ); jump=kill=false; } // void wild() { if (oob) return; if (dead) return; //// Randomly modify the speeds. (Adds energy to the system.) xx += random(-5, +5); yy += random(-5, +5); xx *= random(0.5, 2.5); yy *= random(0.5, 2.5); } }// class Ball // color RED=color(255,0,0), GREEN=color(0,255,0), BLUE=color(0,0,255); color YELLOW=color(255,255,0), CYAN=color(0,255,255), MAGENTA=color(255,0,255); color BROWN=color(127, 0, 0), DARK=color(0, 150, 0), GRASS=color(50, 200, 0); color SKY=color(100, 200, 255), NITE=color(50, 150, 200); color WHITE=color(255), BLACK=color(0), GRAY=color(128), LGRAY=color(172); color PURPLE=color(200, 50, 200), ORANGE=color(255, 127, 0), MAROON=color(250, 50, 150); color[] c = { WHITE, YELLOW, BLUE, RED, PURPLE, ORANGE, GREEN, MAROON, BLACK, YELLOW, BLUE, RED, PURPLE, ORANGE, GREEN, MAROON, 0, 0, 0, 0 }; color[] cn = { 0, 0, YELLOW, 0, 0, 0, 0, 0, WHITE, 0, YELLOW, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; char[] keys = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ')', '!', '@', '#', '$', '%', '^', '&', '*', '(' }; void showColors() { float x=100, y=bottom+25; for (int i=0; i0) news += n+" dead balls were resurrected!\n"; if (no>0) news += no+" OOB were reclaimed.\n"; } void allout() { // Move all balls OOB for (int i=0; i') frames( frameRate * 1.25 ); if (key == '<') frames( frameRate * 0.8 ); // if (key=='+') force *= 1.25; // Shoot harder. if (key=='=') force *= 1.25; if (key=='-') force *= 0.8; if (key=='_') force = 10; if (key == 'W') wild(); if (key == 'w') { wild(); b[0].wild(); news+=" W I L D ! ! !"; newscolor=MAGENTA; background(MAGENTA, 200); } // // Retrieve a ball by number. for (int i=0; i or < to change; F/f for 60/5.\n"; // float x=width/3, y=top+20; fill( 255, 200, 255, 220 ); rectMode(CORNER); rect( x, y, width/2, height/2 ); fill(0); text( s, x+20, y+20 ); fill(127); text( "g: ghosts.", x-50-random(2)+width/2, y-10-random(2)+height/2 ); }