We closed this forum 18 June 2010. It has served us well since 2005 as the ALPHA forum did before it from 2002 to 2005. New discussions are ongoing at the new URL http://forum.processing.org. You'll need to sign up and get a new user account. We're sorry about that inconvenience, but we think it's better in the long run. The content on this forum will remain online.
IndexProgramming Questions & HelpPrograms › Salty Dave's Drawing Machine
Page Index Toggle Pages: 1
Salty Dave's Drawing Machine (Read 870 times)
Salty Dave's Drawing Machine
Feb 1st, 2007, 1:37am
 
I just started using Processing for a college course in intermediate computer graphics and I was given an assignment to create a "drawing machine". I decided to go with something that would also test the things I'm learning in my interactive web media (read "flash games") class. So I came up with this pin-ball style contraption.

This is my first serious program in Processing and although its already been turned in and graded I would really appreciate having a few of you veterans give it a once-over. Any advice that can be given would be plenty welcome, but I do have two specific questions.

If you run the code (it's to long to fit in this post, I'll add it in some replies.) you'll find that the balls continue to speed up over time. I know this is because of the manipulation of the dX and dY arrays with the bounce variable, but I don't understand why the combination of addition and subtraction doesn't balance out over several hundred collisions. I think, if I can remember back to my highschool statistics course, that it should.

More importantly, its obvious that the physics of the bouncing balls isn't spot on. I have wracked my brain and fumbled through several trigonometry web-sites to try and work out realistic post-bouncing trajectories for the balls, but I've come up with next to nothing. If you can help me figure out how to create more realistic bounce trajectories PLEASE let me know!


Re: Salty Dave's Drawing Machine
Reply #1 - Feb 1st, 2007, 1:39am
 
/*
  Salty Dave's Drawing Machine
  An engine designed to allow the user to several
  pylons on the screen. Once the pre-set number of
  pylons is created a pre-set number of balls appear
  in the upper left corner and begin moving about.
  When a ball collides with a pylon it bounces off
  and the pylon generates a colored ring that
  expands and fades away. While the balls are bouncing
  around the user can click on a pylon and drag it to
  change its position.
  */
 
boolean release = true;        //Used to prevent multiple pylons being placed with one click.
boolean ontarget = false;      //Used to determine if the cursor is ontop of a pylon for the click and drag interface.
int pylonTarget;               //Stores the ID number of the pylon the cursor is ontop of.
int pylonTotal = 10;           //Sets the total number of pylons.
int ballTotal = 10;            //Sets the total number of balls.
int[] pylonX = new int[pylonTotal];     //Stores the X coordinates for the pylons.
int[] pylonY = new int[pylonTotal];     //Stores the Y coordinates for the pylons.
int[] ringX = new int[50];     //Stores the X coordinates for the rings.
int[] ringY = new int[50];     //Stores the Y coordinates for the rings.
int[] ringD = new int[50];     //Stores the duration coutners for the rings. This is used to control the ring's size and alpha.
int[] ringR = new int[50];     //Stores the red value for the rings' colors.
int[] ringG = new int[50];     //Stores the green value for the rings' colors.
int[] ringB = new int[50];     //Stores the blue value for the rings' colors.
int ringC = 0;                 //Ring Counter, used to keep track of how many rings exist and to limit the overlapping of ring data.
float[] ballX = new float[ballTotal];  //Stores the X coordinates for the balls.
float[] ballY = new float[ballTotal];  //Stores the Y coordinates for the balls.
float[] dX = new float[ballTotal];     //Stores the X direction value for the balls.
float[] dY = new float[ballTotal];     //Stores the Y direction value for the balls.
float speed = 10;              //Simple speed multiplier.
int pylonCounter = 0;          //Counts the number of pylons currently created during the set up phase of the program.
void setup(){
 frameRate(20);               //Limiting the framerate to try and reduce the slow-down.
 size(400,400);
 noStroke();
 background(255);
 smooth();
 //Initialize the position and direction of all the balls.
 for(int i=0; i<ballTotal; i++){
   ballX[i]=6;
   ballY[i]=6;
   dX[i]=random(0,1);
   dY[i]=1-dX[i];
 }
}

void draw(){
 if(pylonCounter < pylonTotal){   //When there are fewer than 10 pylons on the screen.
   if(mousePressed){
     if(release==true){
       release=false;
       pylonX[pylonCounter] = mouseX;
       pylonY[pylonCounter] = mouseY;

       for(int i=0; i<30; i++){
         fill(0+i*2);
         ellipse(pylonX[pylonCounter]-i/4,pylonY[pylonCounter]-i/4,30-i,30-i);
       }
       pylonCounter++;
     }
   }
   if(mousePressed){
   }
   else {
     release = true;    
   }
 }
 else{  
   //This section is for when all the pylons are on the screen.

   //Erase the screen.
   fill(255);            
   rect(0,0,width,height);

   //Draw the rings.
   for(int i=0; i<50; i++){
     if(ringD[i] != 0 && ringD[i]<150){
       strokeWeight(10);
       stroke(ringR[i],ringG[i],ringB[i],240-ringD[i]*2);
       noFill();
       ellipse(ringX[i],ringY[i],ringD[i]*4,ringD[i]*4);
       ringD[i] = ringD[i]+1;
     }
     else {
       ringD[i] = 0;
     }
   }

   //Draw the pylons.
   noStroke();
   for(int c=0; c<pylonTotal; c++){
     for(int i=0; i<30; i++){
       fill(0+i*2);
       ellipse(pylonX[c]-i/4,pylonY[c]-i/4,30-i,30-i);
     }
   }

   //Drawing the balls.
   for(int c=0; c<ballTotal; c++){
     for(int i=0; i<10; i++){
       fill(40+i*10);
       ellipse(ballX[c]-i/4,ballY[c]-i/4,10-i,10-i);
     }
   }
   //Moving the ball.
   for(int i=0; i<ballTotal; i++){
     ballX[i] += dX[i]*speed;        
     ballY[i] += dY[i]*speed;
   }


   //Constrain the ball to the window.
   for(int i=0; i<ballTotal; i++){
     if (ballX[i] <= 5 || ballX[i] >= width-5){
       dX[i] = dX[i]*-1;
     }
     if (ballY[i] <= 5 || ballY[i] >= height-5){
       dY[i] = dY[i]*-1;
     }
   }

Re: Salty Dave's Drawing Machine
Reply #2 - Feb 1st, 2007, 1:40am
 
/*
   Detect collision of ball and pylon.
   Assign a reversed direction if collision exists.
   Additionally, to limit the odds of a ball becoming trapped between pylons,
   modify the ball's trajectory slightly with the "bounce" variable.
   Initiate an expanding ring at the collision location.
   */
   for(int c=0; c<ballTotal; c++){
     for(int i=0; i<pylonTotal; i++){  
       if(dist(ballX[c],ballY[c],pylonX[i],pylonY[i]) < 20){
         if(ringC >= 50){
           ringC = 0;
         }
         ringX[ringC] = pylonX[i];
         ringY[ringC] = pylonY[i];
         ringD[ringC] = 1;
         ringR[ringC] = int(random(0,255));
         ringG[ringC] = int(random(0,255));
         ringB[ringC] = int(random(0,255));
         ringC += 1;
         translate(pylonX[i],pylonY[i]);
         float angle = degrees(atan2(ballY[c]-pylonY[i],ballX[c]-pylonX[i]));
         float bounce = random(0,.25);
         int r = int(random(0,255));
         int g = int(random(0,255));
         int b = int(random(0,255));
         /*
         The change in ball direction is made according to
         which section of the pylon it strikes.
         There are 8 sections in accordance with the eight directions
         up, up and right, right, down and right, etc.
         */
         //section 1 of 8
         if(angle >= 0 && angle <= 45){
           if(dX[c] < 0){
             dX[c] = dX[c]*-1;
           }
           if(dY[c] < 0){
             dY[c] = dY[c]*-1;
           }
           dX[c] += bounce;
           dY[c] -= .25-bounce;
         }

         //section 2 of 8
         else if(angle >= 45 && angle <= 90){
           if(dX[c] < 0){
             dX[c] = dX[c]*-1;
           }
           if(dY[c] < 0){
             dY[c] = dY[c]*-1;
           }
           dY[c] += bounce;
           dX[c] -= .25-bounce;
         }

         //section 3 of 8
         else if(angle >= 90 && angle <= 135){
           if(dX[c] > 0){
             dX[c] = dX[c]*-1;
           }
           if(dY[c] < 0){
             dY[c] = dY[c]*-1;
           }
           dY[c] += bounce;
           dX[c] -= .25-bounce;
         }

         //section 4 of 8
         else if(angle >= 135 && angle <= 180){
           if(dX[c] > 0){
             dX[c] = dX[c]*-1;
           }
           if(dY[c] < 0){
             dY[c] = dY[c]*-1;
           }
           dX[c] -= bounce;
           dY[c] += .25-bounce;
         }  

         //section 5 of 8
         else if(angle >= -180 && angle <= -135){
           if(dX[c] > 0){
             dX[c] = dX[c]*-1;
           }
           if(dY[c] > 0){
             dY[c] = dY[c]*-1;
           }
           dX[c] -= bounce;
           dY[c] += .25-bounce;
         }

         //section 6 of 8
         else if(angle >= -135 && angle <= -90){
           if(dX[c] > 0){
             dX[c] = dX[c]*-1;
           }
           if(dY[c] > 0){
             dY[c] = dY[c]*-1;
           }
           dY[c] -= bounce;
           dX[c] += .25-bounce;
         }

         //section 7 of 8
         else if(angle >= -90 && angle <= -45){
           if(dX[c] < 0){
             dX[c] = dX[c]*-1;
           }
           if(dY[c] > 0){
             dY[c] = dY[c]*-1;
           }
           dY[c] -= bounce;
           dX[c] += .25-bounce;
         }

         //section 8 of 8
         else if(angle >= -90 && angle <= 0){
           if(dX[c] < 0){
             dX[c] = dX[c]*-1;
           }
           if(dY[c] > 0){
             dY[c] = dY[c]*-1;
           }
           dX[c] += bounce;
           dY[c] -= .25-bounce;
         }

       }
     }
   }
   //Check if the mouse is currently over a pylon and if so
   //assign that pylons ID number to pylonTarget.
   for(int i=0; i<pylonTotal; i++){
     if(mouseX <= pylonX[i]+15 && mouseX >= pylonX[i]-15 && mouseY <= pylonY[i]+15 && mouseY >= pylonY[i]-15){
       ontarget = true;
       pylonTarget = i;
     }
   }
   //If the mouse is clicked on a pylon, start dragging that pylon.
   if(mousePressed && ontarget == true){
     pylonX[pylonTarget]=mouseX;
     pylonY[pylonTarget]=mouseY;
     ontarget = true;
   } else {
   ontarget = false;
   }
 }
}
Re: Salty Dave's Drawing Machine
Reply #3 - Feb 1st, 2007, 3:58am
 
The fact that you want to push this after receiving your grade warms my heart. I actually posted something recently that might be of help (with the reflection problem.) http://processing.org/discourse/yabb_beta/YaBB.cgi?board=Syntax;action=display;num=1167889216
Check out my last reply in the thread.

My example was about an object being repelled by the mouse, but it's the same basic reflection problem (since only one object is being reflected-the problem is a little harder for ball-ball collision). The equation that will give you more accurate ball-post reflection is:  R = 2N(N•L)-L
R = reflection vector (what you want)
N = normal vector
L = the incidence vector
(N and L need to be normalized)

If this doesn't ring any bells (possibly covered somewhere in Intro CG course), the link gives some more info and an example.

Hope this helps some and please post any updates.
I also hope you got a decent grade on the 1st version Wink
Ira
Re: Salty Dave's Drawing Machine
Reply #4 - Feb 2nd, 2007, 12:23am
 
First off, I don't know if I should call you Iraq (your forum name) or Ira (the name you signed your post with), but in either case you're my new best friend. Vector math having been little more than a foot note in my life it never occured to me to turn to it, despite the obvious applicability. The code in your example has done wonders for me. I'll post some new code in another reply that includes your vector physics as well as a few other fixes to smooth out the program's running.

Unfortunately, as I mentioned, vector math is little more than a foot note in my life, so although I can implement the equations you showed me, I don't fully understand them. If you can direct me to any sites that might help me understand them I would really appreciate it.
Re: Salty Dave's Drawing Machine
Reply #5 - Feb 2nd, 2007, 12:25am
 
/*
Salty Dave's Drawing Machine
An engine designed to allow the user to several
pylons on the screen. Once the pre-set number of
pylons is created a pre-set number of balls appear
in the upper left corner and begin moving about.
When a ball collides with a pylon it bounces off
and the pylon generates a colored ring that
expands and fades away. While the balls are bouncing
around the user can click on a pylon and drag it to
change its position.
*/

boolean release = true;        //Used to prevent multiple pylons being placed with one click.
boolean ontarget = false;      //Used to determine if the cursor is ontop of a pylon for the click and drag interface.
int pylonTarget;               //Stores the ID number of the pylon the cursor is ontop of.
int pylonTotal = 10;           //Sets the total number of pylons.
int ballTotal = 10;            //Sets the total number of balls.
int[] pylonX = new int[pylonTotal];     //Stores the X coordinates for the pylons.
int[] pylonY = new int[pylonTotal];     //Stores the Y coordinates for the pylons.
int[] ringX = new int[50];     //Stores the X coordinates for the rings.
int[] ringY = new int[50];     //Stores the Y coordinates for the rings.
int[] ringD = new int[50];     //Stores the duration coutners for the rings. This is used to control the ring's size and alpha.
int[] ringR = new int[50];     //Stores the red value for the rings' colors.
int[] ringG = new int[50];     //Stores the green value for the rings' colors.
int[] ringB = new int[50];     //Stores the blue value for the rings' colors.
int ringC = 0;                 //Ring Counter, used to keep track of how many rings exist and to limit the overlapping of ring data.
float[] ballX = new float[ballTotal];  //Stores the X coordinates for the balls.
float[] ballY = new float[ballTotal];  //Stores the Y coordinates for the balls.
float[] PballX = new float[ballTotal];  //Stores the previous significant X coordinates for the balls.
float[] PballY = new float[ballTotal];  //Stores the previous significant Y coordinates for the balls.
float[] dX = new float[ballTotal];     //Stores the X direction vectors for the balls.
float[] dY = new float[ballTotal];     //Stores the Y direction vectors for the balls.
float speed = 8;              //Simple speed multiplier.
int pylonCounter = 0;          //Counts the number of pylons currently created during the set up phase of the program.
void setup(){
 frameRate(20);               //Limiting the framerate to try and reduce the slow-down.
 size(400,400);
 noStroke();
 background(255);
 smooth();
 //Initialize the position and direction of all the balls.
 for(int i=0; i<ballTotal; i++){
   PballX[i]=6;
   PballY[i]=6;
   ballX[i]=6;
   ballY[i]=6;
   dX[i]=random(0,1);
   dY[i]=1-dX[i];
 }
}

void draw(){
 if(pylonCounter < pylonTotal){   //When there are fewer than 10 pylons on the screen.
   if(mousePressed){
     if(release==true){
       release=false;
       pylonX[pylonCounter] = mouseX;
       pylonY[pylonCounter] = mouseY;

       for(int i=0; i<30; i++){
         fill(0+i*2);
         ellipse(pylonX[pylonCounter]-i/4,pylonY[pylonCounter]-i/4,30-i,30-i);
       }
       pylonCounter++;
     }
   }
   if(mousePressed){
   }
   else {
     release = true;    
   }
 }
 else{  
   //This section is for when all the pylons are on the screen.

   //Erase the screen.
   fill(255);            
   rect(0,0,width,height);

   //Draw the rings.
   for(int i=0; i<50; i++){
     if(ringD[i] != 0 && ringD[i]<100){
       strokeWeight(10-ringD[i]/10);
       stroke(ringR[i],ringG[i],ringB[i]);
       noFill();
       ellipse(ringX[i],ringY[i],ringD[i]*4,ringD[i]*4);
       ringD[i] = ringD[i]+1;
     }
     else {
       ringD[i] = 0;
     }
   }

   //Draw the pylons.
   noStroke();
   for(int c=0; c<pylonTotal; c++){
     for(int i=0; i<30; i++){
       fill(0+i*2);
       ellipse(pylonX[c]-i/4,pylonY[c]-i/4,30-i,30-i);
     }
   }

   //Drawing the balls.
   for(int c=0; c<ballTotal; c++){
     for(int i=0; i<10; i++){
       fill(40+i*10);
       ellipse(ballX[c]-i/4,ballY[c]-i/4,10-i,10-i);
     }
   }
   //Moving the ball.
   for(int i=0; i<ballTotal; i++){
     ballX[i] += dX[i]*speed;        
     ballY[i] += dY[i]*speed;
   }


   //Constrain the ball to the window.
   for(int i=0; i<ballTotal; i++){
     if (ballX[i] <= 5){
       dX[i] = dX[i]*-1;
       ballX[i] = 6;
     }
     if(ballX[i] >= width-5){
       dX[i] = dX[i]*-1;
       ballX[i] = width-6;
     }
     if (ballY[i] <= 5){
       dY[i] = dY[i]*-1;
       ballY[i] = 6;
     }
     if(ballY[i] >= height-5){
       dY[i] = dY[i]*-1;
       ballY[i] = height-6;
     }
   }
Re: Salty Dave's Drawing Machine
Reply #6 - Feb 2nd, 2007, 12:26am
 
/*
   Detect collision of ball and pylon.
    Calculate the balls new vector using R=2N(N*L)-L equation if collision exists.
    Initiate an expanding ring at the collision location.
    */
   for(int c=0; c<ballTotal; c++){
     for(int i=0; i<pylonTotal; i++){
       float ballDist = dist(ballX[c],ballY[c],pylonX[i],pylonY[i]); //Find distance between the ball and the pylon.
       if( ballDist < 20){
         //Check the number of rings and generate a new one.
         if(ringC >= 50){
           ringC = 0;
         }
         ringX[ringC] = pylonX[i];
         ringY[ringC] = pylonY[i];
         ringD[ringC] = 1;
         ringR[ringC] = int(random(0,255));
         ringG[ringC] = int(random(0,255));
         ringB[ringC] = int(random(0,255));
         ringC += 1;

         //Determine the ball's incoming vector (L) for X and Y.
         float vectorLX = PballX[c]-ballX[c];
         float vectorLY = PballY[c]-ballY[c];
         //Normalize the vector by dividing it by the distance.
         float vectorLdist = dist(PballX[c],PballY[c],ballX[c],ballY[c]);
         vectorLX /= vectorLdist;
         vectorLY /= vectorLdist;

         //Determine the counter vector (N) for X and Y.
         float vectorNX = ballX[c]-pylonX[i];
         float vectorNY = ballY[c]-pylonY[i];
         //Normalize the vector by dividing it by the distance.
         vectorNX /= ballDist;          
         vectorNY /= ballDist;

         //Perform the (N*L) part of the calculation.
         float NL = vectorLX*vectorNX + vectorLY*vectorNY;

         //Perform the rest of the calculation to get the new direction vectors.
         dX[c] = 2 * vectorNX * NL - vectorLX;
         dY[c] = 2 * vectorNY * NL - vectorLY;

         //Shift the ball along the counter vector (n)
         //to avoid overlap with the pylon.
         ballX[c] = pylonX[i] + 20*vectorNX;
         ballY[c] = pylonY[i] + 20*vectorNY;

         //Mark this collision as the ball's last significant position.
         PballX[c] = ballX[c];
         PballY[c] = ballY[c];
       }
     }
   }
   //Check if the mouse is currently over a pylon and if so
   //assign that pylons ID number to pylonTarget.
   for(int i=0; i<pylonTotal; i++){
     if(mouseX <= pylonX[i]+15 && mouseX >= pylonX[i]-15 && mouseY <= pylonY[i]+15 && mouseY >= pylonY[i]-15){
       ontarget = true;
       pylonTarget = i;
     }
   }
   //If the mouse is clicked on a pylon, start dragging that pylon.
   if(mousePressed && ontarget == true){
     pylonX[pylonTarget]=mouseX;
     pylonY[pylonTarget]=mouseY;
     ontarget = true;
   }
   else {
     ontarget = false;
   }
 }
}

Page Index Toggle Pages: 1