Help please! - Ball screensaver

I want the ball to bounce of all edges of the screen as well as the center spinning rectangle, but the ball just ejects out of the rectangle and falls down.

float r = 0;
float ballx;
float bally;
float IIballx;
float IIbally;
float xspeed = 5;
float yspeed = 8;
float IIxspeed = 7;
float IIyspeed = 8;
float IIIballx;
float IIIbally;
float IIIxspeed = 6;
float IIIyspeed = 8;
float backR;
float backG;
float backB;

void setup(){
fullScreen();

backR = random(0, 100); //variables for random background
backG = random(0, 100);
backB = random(0, 100);
background(backR, backG, backB);

rectMode(CENTER);

}

void draw(){
background(backR, backG, backB);
translate(width/2, height/2);
rotate(r);
rect(0, 0, 100, 400);
r = r + 0.01;

stroke(255);
ellipse(ballx, bally, 32, 32);
ellipse(IIballx, IIbally, 32, 32);
ellipse(IIIballx, IIIbally, 32, 32);

if(sideCollision(ballx) || (sideCollision(IIballx) || (sideCollision(IIIballx)))){
xspeed = xspeed * -1;
}

if(topOrBottomCollision(ballx, bally) || (topOrBottomCollision(IIballx, IIbally) || (topOrBottomCollision(IIIballx, IIIbally)))){
yspeed = yspeed * -1;
}

ballx = ballx + xspeed;
bally = bally + yspeed;
IIballx = IIballx + IIxspeed;
IIbally = IIbally + IIyspeed;
IIIballx = IIIballx + IIIxspeed;
IIIbally = IIIbally + IIIyspeed;

}

boolean sideCollision(float x){
if(x<0 || x>width){
return true;
} else {
return false;
}
}

boolean topOrBottomCollision(float x, float y){
if((x>200 && x<1000 && y>290 && y<500)|| (y<0 || y>height)){
return true;
} else {
return false;

}
}

Yikes, your code is all over the place and sort of a mess.
You have so many variables and collision functions that I’m not sure where to start.

Let’s start over. First, a perfectly good, working blank sketch:

void setup(){
  fullScreen();
}

void draw(){
  background(0);
}

First, let’s deal with the random background. But not in the way you did. Let’s write a class to contain the logic for it, so it’s not getting in the way of other things.

class RandomBackground {
  float r,g,b;
  RandomBackground(){
    r = random(255);
    g = random(255);
    b = random(255);
  }
  void draw(){
    background(r,g,b);
  }
}

RandomBackground bg = new RandomBackground();

void setup(){
  fullScreen();  
}

void draw(){
  bg.draw();
}

Alright, that works. Let’s keep adding things that work, and that way when we’re done, we’ll have a sketch that works.

Next, let’s add some balls. Since each Ball will basically be doing its own thing, write just the one Ball class and use it over and over for each ball.

class RandomBackground {
  float r, g, b;
  RandomBackground() {
    r = random(255);
    g = random(255);
    b = random(255);
  }
  void draw() {
    background(r, g, b);
  }
}

// -----

class Ball {
  float x, y, dx, dy;
  Ball() {
    x = random(width);
    y = random(height);
    float r = random(TWO_PI);
    dx = 10 * cos(r);
    dy = 10 * sin(r);
  }
  void draw() {
    move();
    noStroke();
    fill(255);
    pushMatrix();
    translate(x, y);
    ellipse(0, 0, 20, 20);
    popMatrix();
  }
  void move(){
    x+=dx;
    y+=dy;
  }
}

// -----

RandomBackground bg = new RandomBackground();
Ball[] balls = new Ball[3];

void setup() {
  fullScreen();
  // Create all balls.
  for( int i = 0; i < balls.length; i++) balls[i] = new Ball();
}

void draw() {
  bg.draw();
  // Draw all balls.
  for( int i = 0; i < balls.length; i++) balls[i].draw();
}

Notice that each ball move because its move() function moves it. They don’t bounce yet, because we haven’t added code to see if they should bounce. Let’s do that now.

class RandomBackground {
  float r, g, b;
  RandomBackground() {
    r = random(255);
    g = random(255);
    b = random(255);
  }
  void draw() {
    background(r, g, b);
  }
}

// -----

class Ball {
  float x, y, dx, dy;
  Ball() {
    x = random(width);
    y = random(height);
    float r = random(TWO_PI);
    dx = 10 * cos(r);
    dy = 10 * sin(r);
  }
  void draw() {
    move();
    noStroke();
    fill(255);
    pushMatrix();
    translate(x, y);
    ellipse(0, 0, 20, 20);
    popMatrix();
  }
  void move(){
    // Move horizontally.
    x+=dx;
    // Bounce left side?
    if( x < 10 ){
      x = 10;
      dx *= -1;
    }
    // Bounce right side?
    if( x > width - 10 ){
      x = width - 10;
      dx *= -1;
    } 
    // Move vertically.
    y+=dy;
    // Bounce top?
    if( y < 10 ){
      y = 10;
      dy *= -1;
    }
    // Bounce bottom?
    if( y > height - 10 ){
      y = height - 10;
      dy *= -1;
    }
  }
}

// -----

RandomBackground bg = new RandomBackground();
Ball[] balls = new Ball[33];

void setup() {
  fullScreen();
  // Create all balls.
  for( int i = 0; i < balls.length; i++) balls[i] = new Ball();
}

void draw() {
  bg.draw();
  // Draw all balls.
  for( int i = 0; i < balls.length; i++) balls[i].draw();
}

There we go. I added some more balls too, to keep it from being boring.

Ah, but now we come to the real problem! How can we get a Ball to bounce off a spinning rectangle?

The spinning rectangle is spinning, you see, so the normal, nice & easy way of checking for circle-rectangle collision isn’t going to work. We’ll have to step it up and go with circle-polygon collision!

But before we get to that, we need the rectangle. Let’s remove the balls for now and add one. Notice that while I could just plop in a rectangle and spin it, what I really need are the positions of the four corners of the spinning rectangle (for collision purposes), so it’d be nice to have those handy too!

If you do a little math, you can work out that a normal, unspun rectangle has a corner at

(
dist(0,0,200,50) * cos(atan2(200,50)),
dist(0,0,200,50) * sin(atan2(200,50))
)

No wait, that doesn’t seem right. Or maybe it is?
This is too complicated. I’m confused.

Let’s just use screenX() and screenY() instead, and make the whole thing simple!

class RandomBackground {
  float r, g, b;
  RandomBackground() {
    r = random(255);
    g = random(255);
    b = random(255);
  }
  void draw() {
    background(r, g, b);
  }
}

// -----

class Ball {
  float x, y, dx, dy;
  Ball() {
    x = random(width);
    y = random(height);
    float r = random(TWO_PI);
    dx = 10 * cos(r);
    dy = 10 * sin(r);
  }
  void draw() {
    move();
    noStroke();
    fill(255);
    pushMatrix();
    translate(x, y);
    ellipse(0, 0, 20, 20);
    popMatrix();
  }
  void move() {
    // Move horizontally.
    x+=dx;
    // Bounce left side?
    if ( x < 10 ) {
      x = 10;
      dx *= -1;
    }
    // Bounce right side?
    if ( x > width - 10 ) {
      x = width - 10;
      dx *= -1;
    } 
    // Move vertically.
    y+=dy;
    // Bounce top?
    if ( y < 10 ) {
      y = 10;
      dy *= -1;
    }
    // Bounce bottom?
    if ( y > height - 10 ) {
      y = height - 10;
      dy *= -1;
    }
  }
}

// -----

float[] rxs = {0,0,0,0};
float[] rys = {0,0,0,0};

class SpinningRect {
  SpinningRect() {
  }
  void draw() {
    pushMatrix();
    translate(width/2, height/2);
    noFill();
    stroke(255);
    rectMode(CENTER);
    rotate(map(millis()%5000,0,5000,0,TWO_PI));
    rect(0, 0, 400, 100);
    // Not ideal in general, but works for now.
    rxs[0] = screenX(200,-50);
    rys[0] = screenY(200,-50);
    rxs[1] = screenX(200,50);
    rys[1] = screenY(200,50);
    rxs[2] = screenX(-200,50);
    rys[2] = screenY(-200,50);
    rxs[3] = screenX(-200,-50);
    rys[3] = screenY(-200,-50);    
    popMatrix();
    
    stroke(255,0,0);
    ellipse(rxs[0], rys[0],5,5);
    ellipse(rxs[1], rys[1],5,5);
    ellipse(rxs[2], rys[2],5,5);
    ellipse(rxs[3], rys[3],5,5);
  }
}

// -----

RandomBackground bg = new RandomBackground();
Ball[] balls = new Ball[33];
SpinningRect sr = new SpinningRect();

void setup() {
  fullScreen();
  // Create all balls.
  for ( int i = 0; i < balls.length; i++) balls[i] = new Ball();
}

void draw() {
  bg.draw();
  // Don't draw all balls for now. They're in the way.
  // for( int i = 0; i < balls.length; i++) balls[i].draw();
  // Draw the spinning rectangle.
  sr.draw();
}

There, now you can see that we can track the four corners of the spinning rectangle.


So now we throw in the collision code, make the rectangle solid, put the balls back in, and make them bounce randomly if they hit the rectangle. AND…

class RandomBackground {
  float r, g, b;
  RandomBackground() {
    r = random(255);
    g = random(255);
    b = random(255);
  }
  void draw() {
    background(r, g, b);
  }
}

// -----

class Ball {
  float x, y, dx, dy;
  Ball() {
    x = random(width);
    y = random(height);
    new_direction();
  }
  void new_direction() {
    float r = random(TWO_PI);
    dx = 10 * cos(r);
    dy = 10 * sin(r);
  }
  void draw() {
    move();
    noStroke();
    fill(255);
    pushMatrix();
    translate(x, y);
    ellipse(0, 0, 20, 20);
    popMatrix();
  }
  void move() {
    // Move horizontally.
    x+=dx;
    // Bounce left side?
    if ( x < 10 ) {
      x = 10;
      dx *= -1;
    }
    // Bounce right side?
    if ( x > width - 10 ) {
      x = width - 10;
      dx *= -1;
    } 
    // Move vertically.
    y+=dy;
    // Bounce top?
    if ( y < 10 ) {
      y = 10;
      dy *= -1;
    }
    // Bounce bottom?
    if ( y > height - 10 ) {
      y = height - 10;
      dy *= -1;
    }
    // Colliding with the rectangle('s edges)? Move in a new random direction.
    if ( polyCircle(verts, x, y, 10) ) {
      new_direction();
    }
  }
}

// -----

// Tracks the corners of the spinning rectangle.
PVector[] verts = new PVector[4];

class SpinningRect {
  SpinningRect() {
    for ( int i = 0; i < verts.length; i++) verts[i] = new PVector(0, 0, 0);
  }
  void draw() {
    pushMatrix();
    translate(width/2, height/2);
    fill(255);
    noStroke();
    rectMode(CENTER);
    rotate(map(millis()%5000, 0, 5000, 0, TWO_PI));
    rect(0, 0, 400, 100);
    // Update where the corners are known to be.
    verts[0].x = screenX(200, -50);
    verts[0].y = screenY(200, -50);
    verts[1].x = screenX(200, 50);
    verts[1].y = screenY(200, 50);
    verts[2].x = screenX(-200, 50);
    verts[2].y = screenY(-200, 50);
    verts[3].x = screenX(-200, -50);
    verts[3].y = screenY(-200, -50);     
    popMatrix();
  }
}

// -----

RandomBackground bg = new RandomBackground();
Ball[] balls = new Ball[33];
SpinningRect sr = new SpinningRect();

void setup() {
  fullScreen();
  // Create all balls.
  for ( int i = 0; i < balls.length; i++) balls[i] = new Ball();
}

void draw() {
  bg.draw();
  // Draw the spinning rectangle.
  // Do this first since that will update knowing where its corners are.
  sr.draw();
  // Draw all the balls.
  for ( int i = 0; i < balls.length; i++) balls[i].draw();
}




// ----- COLLISION CODE FOLLOWS - CONSIDER MOVING TO ITS OWN TAB! ;-)

// POLYGON/CIRCLE
boolean polyCircle(PVector[] vertices, float cx, float cy, float r) {

  // go through each of the vertices, plus
  // the next vertex in the list
  int next = 0;
  for (int current=0; current<vertices.length; current++) {

    // get next vertex in list
    // if we've hit the end, wrap around to 0
    next = current+1;
    if (next == vertices.length) next = 0;

    // get the PVectors at our current position
    // this makes our if statement a little cleaner
    PVector vc = vertices[current];    // c for "current"
    PVector vn = vertices[next];       // n for "next"

    // check for collision between the circle and
    // a line formed between the two vertices
    boolean collision = lineCircle(vc.x, vc.y, vn.x, vn.y, cx, cy, r);
    if (collision) return true;
  }

  // the above algorithm only checks if the circle
  // is touching the edges of the polygon – in most
  // cases this is enough, but you can un-comment the
  // following code to also test if the center of the
  // circle is inside the polygon

  // boolean centerInside = polygonPoint(vertices, cx,cy);
  // if (centerInside) return true;

  // otherwise, after all that, return false
  return false;
}

// LINE/CIRCLE
boolean lineCircle(float x1, float y1, float x2, float y2, float cx, float cy, float r) {

  // is either end INSIDE the circle?
  // if so, return true immediately
  boolean inside1 = pointCircle(x1, y1, cx, cy, r);
  boolean inside2 = pointCircle(x2, y2, cx, cy, r);
  if (inside1 || inside2) return true;

  // get length of the line
  float distX = x1 - x2;
  float distY = y1 - y2;
  float len = sqrt( (distX*distX) + (distY*distY) );

  // get dot product of the line and circle
  float dot = ( ((cx-x1)*(x2-x1)) + ((cy-y1)*(y2-y1)) ) / pow(len, 2);

  // find the closest point on the line
  float closestX = x1 + (dot * (x2-x1));
  float closestY = y1 + (dot * (y2-y1));

  // is this point actually on the line segment?
  // if so keep going, but if not, return false
  boolean onSegment = linePoint(x1, y1, x2, y2, closestX, closestY);
  if (!onSegment) return false;

  // optionally, draw a circle at the closest point
  // on the line
  //fill(255, 0, 0);
  //noStroke();
  //ellipse(closestX, closestY, 20, 20);

  // get distance to closest point
  distX = closestX - cx;
  distY = closestY - cy;
  float distance = sqrt( (distX*distX) + (distY*distY) );

  // is the circle on the line?
  if (distance <= r) {
    return true;
  }
  return false;
}

// LINE/POINT
boolean linePoint(float x1, float y1, float x2, float y2, float px, float py) {

  // get distance from the point to the two ends of the line
  float d1 = dist(px, py, x1, y1);
  float d2 = dist(px, py, x2, y2);

  // get the length of the line
  float lineLen = dist(x1, y1, x2, y2);

  // since floats are so minutely accurate, add
  // a little buffer zone that will give collision
  float buffer = 0.1;    // higher # = less accurate

  // if the two distances are equal to the line's
  // length, the point is on the line!
  // note we use the buffer here to give a range, rather
  // than one #
  if (d1+d2 >= lineLen-buffer && d1+d2 <= lineLen+buffer) {
    return true;
  }
  return false;
}

// POINT/CIRCLE
boolean pointCircle(float px, float py, float cx, float cy, float r) {

  // get distance between the point and circle's center
  // using the Pythagorean Theorem
  float distX = px - cx;
  float distY = py - cy;
  float distance = sqrt( (distX*distX) + (distY*distY) );

  // if the distance is less than the circle's 
  // radius the point is inside!
  if (distance <= r) {
    return true;
  }
  return false;
}

Well, it almost works. Sometimes the balls bounce off without a problem. But sometimes they get stuck on the rectangle and jitter. This happens because they move in a new direction that just so happens to put them on the rectangle still. Any ideas on how we can fix that?

Think about it.