simple rect collision from array

edited February 2017 in Questions about Code

dear all, i'm an absolute beginner in processing and i'm struggling with a (simple?) problem. please bare with me, as I'm sure the code is catastrophic and a lot of my terminology here is wrong :)

basically i'm trying to achieve a simple rectangle collision, using an array and a class. it almost works, but sometimes, rectangles don't collide/rebound, but pass through one another.. i have no clue why this is.

i'd be gracious for any help!

Bouncer[] bouncers = new Bouncer[0];
float resize = 30;
float minsize = 2;
float maxsize = 50;
float freq = 440;
float amp = 0;
float globspeed = 3.;
float boxes = 5;
float margin = 5;

void setup() {
  //frameRate(10);
  size(700, 400);
  resize = minsize;
  pmouseY = mouseY;
}

void draw() {

  background(255);
  if (mousePressed) {
    resize += 2;
    noStroke();
    rectMode(CENTER);
    fill(50);
    resize = constrain(resize, minsize, maxsize);
    rect (mouseX, mouseY, resize, resize);
  }



  for (int i = 0; i < bouncers.length; i++ ) {
    bouncers[i].move(); 
    bouncers[i].create();
    bouncers[i].collide();
  }


  float onebox = ((width - ((boxes+1)*margin)) / boxes );


  for (float i = 1; i <= boxes; i++ ) {
    rectMode(CORNER);
    noStroke();
    fill(50);
    rect((i-1.)*(onebox+margin)+margin, height-20, onebox, 20-margin);
  }
}

void mouseReleased() {
  Bouncer anotherone = new Bouncer(mouseX-resize/2, mouseY-resize/2, resize, resize-resize/2*(globspeed*-1), bouncers.length, 50);
  bouncers = (Bouncer[]) append(bouncers, anotherone);
  resize = minsize;
}

here's my class declaration:

class Bouncer {
  float posX;
  float posY;
  float size;
  float speed;
  //SinOsc sine;
  float freq = 440;
  float gravity = 1;
  int iam;
  float col;


  Bouncer(float posX_, float posY_, float size_, float speed_, int iam_, float col_) {
    posX = posX_;
    posY = posY_;
    size = size_;
    speed = speed_/10;
    iam = iam_;
    col = col_;
  }

  void create() {
    rectMode(CORNER);
    noStroke();
    fill(col);
    rect(posX, posY, size, size);
    //println(iam);
  }


  void collide() {
    for (int io = 0; io < bouncers.length; io++) {
      float x1 = bouncers[iam].posX;
      float y1 = bouncers[iam].posY;
      float s1 = bouncers[iam].size;
      float x2 = bouncers[io].posX;
      float y2 = bouncers[io].posY;
      float s2 = bouncers[io].size;
      if (x1+s1/2 >= x2-s2/2 && x1-s1/2 <= x2+s2/2) 
      { 
        if (y1+s1 >= y2 && y1 <= y2+s2) {
          bouncers[iam].speed *= -1;
          bouncers[io].speed *= -1;
          bouncers[io].col = 150;
        }
      }
    }
  }

  void move() {
    posY = posY + speed; 
    if (posY > height-size-20) {
      speed *= -1; 
      posY = height-size-20;
    } else  if (posY <= 0) {
      posY = 1;
      speed *= -1 ; 
    }
  }
}
Tagged:

Answers

  • checking box 1 (i = 1 on line 32) against box 2 (io = 2 on line 32 of the class). they collide, so change the direction of both of them.

    milliseconds later you compare box 2 (i = 2 on line 32) against box 1 (io = 1 on line 32 of the class). they are STILL colliding (box 1 hasn't moved, box2 has moved one step, using its new speed, changed above) so change the direction of both...

    see the problem?

  • thanks so much koogs for jumping in.. i'm not sure if i understand... as soon as i change both their directions (line 43+43) – shouldn't they stop colliding?

    got another hint for me novice? thanks (:

  • You change the directions at that point but you don't move them so they are still colliding.

    You then move the second one, but if it's moving slower than the first the new position could still be colliding.

    For instance: If the first one is moving at 10 units per click then the overlap on the first check might be 8 units. You swap directions and move the second block before retesting. If that's only moving 5 units per click it'll never escape.

    This stuff is tricky.

  • edited February 2017 Answer ✓

    @benniii --

    If two shapes overlap deeply before changing direction, they will "vibrate" -- changing direction constantly, but never escaping. Different strategies to avoid these kinds of problems:

    1. wait a certain amount of time / number of frames after colliding before rechecking for collision
    2. set an "overlapping" flag -- once it bounces, don't let it bounce again until it is not overlapping
    3. keep a list of overlapping objects -- never bounce something off another thing that it is already listed as overlapping with
    4. make sure that your minimum speeds are always greater than your maximum overlap -- for example, always check the fastest moving object
    5. have a bounce / de-overlap adjustment that actually separates the two overlapping objects and is computed differently from simply changing direction and then applying normal momentum.

    ...etc.

  • thanks for these tips, guys... jeee... i have to admit, i have not a clue as for how to do this. i wouldn't have imagined that this simple "bounce and collision" task was such a challenge... mh

  • Answer ✓

    It might be enough to only change the direction of one of the two colliding items, rather than both.

    Or stop checking both A against B and then B against A by only checking items that come later in the list than the one currently being checked.

  • thanks a ton for all your help, folks! i think i was kind of able to solve it thanks to the both of your tips... i'm only changing the direction for one rectangle and i'm waiting several frames after collision.

    oh, there's so much to learn!

  • edited March 2017

    This is a collision code using circles. I found this code at: https://processing.org/examples/circlecollision.html

    However, when you add more objects to it, and their radius are alike, they will experience the vibration/jittering that @jeremydouglass mentioned. Here is a slightly modified version of the code to see the effect. Let it run for few seconds or a cpl of minutes to see the jittering effect.

    New objects are added with random radius within a range of 50% to 100% of the value of R. The value of n defines the number of objects to instantiate in the scene.

    I like method #2 described by Jeremy above. One would need to consider extreme cases were multiple bouncing occurs. I will keep a list of overlapping object pairs or an alternative is for each object to have a list of what other objects it has experienced overlapped with.

    Kf

    ArrayList<Ball> balls;
    int n=20;
    int R=20;
    
    void setup() {
      size(640, 360);
      balls = new ArrayList();
    
    
    
      //Add new objects but first checking they do not overlap
      for (int i=0; i<n; i++) {
        int ax=0;
        int ay=0;
        int rr=int(random(0.5*R,R));
        boolean overlapDetected=true; 
    
        //NEXT "while loop" ensures that the new object to be added to the arraylist 
        //     doesn't overlap with existing objects in the list
        while (overlapDetected==true) {
          overlapDetected=false; //Change to false so for the algorithm to work
          ax=int(random(width));
          ay=int(random(height));       
          for (int j=0; j<i && !overlapDetected; j++) {
            float d=dist(ax, ay, balls.get(j).position.x, balls.get(j).position.y);
            if (d<rr + balls.get(j).r)
              overlapDetected=true;
          }
        }
    
        //Add object to list
        balls.add(new Ball(ax, ay, rr));
      }
    }
    
    void draw() {
      background(51);
    
      for (int i=0; i<n; i++) {
        Ball b=balls.get(i);
        b.update();
        b.display();
        b.checkBoundaryCollision();
        for (int j=i+1; j<n; j++) {
          balls.get(i).checkCollision(balls.get(j));
        }
      }
    }
    
    
    
    
    
    
    
    class Ball {
      PVector position;
      PVector velocity;
    
      float r, m;
    
      Ball(float x, float y, float r_) {
        position = new PVector(x, y);
        velocity = PVector.random2D();
        velocity.mult(3);
        r = r_;
        m = r*.1;
      }
    
      void update() {
        position.add(velocity);
      }
    
      void checkBoundaryCollision() {
        if (position.x > width-r) {
          position.x = width-r;
          velocity.x *= -1;
        } else if (position.x < r) {
          position.x = r;
          velocity.x *= -1;
        } else if (position.y > height-r) {
          position.y = height-r;
          velocity.y *= -1;
        } else if (position.y < r) {
          position.y = r;
          velocity.y *= -1;
        }
      }
    
      void checkCollision(Ball other) {
    
        // get distances between the balls components
        PVector bVect = PVector.sub(other.position, position);
    
        // calculate magnitude of the vector separating the balls
        float bVectMag = bVect.mag();
    
        if (bVectMag < r + other.r) {
          // get angle of bVect
          float theta  = bVect.heading();
          // precalculate trig values
          float sine = sin(theta);
          float cosine = cos(theta);
    
          /*** bTemp will hold rotated ball positions. You 
           just need to worry about bTemp[1] position*/
          PVector[] bTemp = {
            new PVector(), new PVector()
          };
    
          /*** this ball's position is relative to the other
           so you can use the vector between them (bVect) as the 
           reference point in the rotation expressions.
           bTemp[0].position.x and bTemp[0].position.y will initialize
           automatically to 0.0, which is what you want
           since b[1] will rotate around b[0] */
          bTemp[1].x  = cosine * bVect.x + sine * bVect.y;
          bTemp[1].y  = cosine * bVect.y - sine * bVect.x;
    
          // rotate Temporary velocities
          PVector[] vTemp = {
            new PVector(), new PVector()
          };
    
          vTemp[0].x  = cosine * velocity.x + sine * velocity.y;
          vTemp[0].y  = cosine * velocity.y - sine * velocity.x;
          vTemp[1].x  = cosine * other.velocity.x + sine * other.velocity.y;
          vTemp[1].y  = cosine * other.velocity.y - sine * other.velocity.x;
    
          /***  Now that velocities are rotated, you can use 1D
           conservation of momentum equations to calculate 
           the final velocity along the x-axis. */
          PVector[] vFinal = {  
            new PVector(), new PVector()
          };
    
          // final rotated velocity for b[0]
          vFinal[0].x = ((m - other.m) * vTemp[0].x + 2 * other.m * vTemp[1].x) / (m + other.m);
          vFinal[0].y = vTemp[0].y;
    
          // final rotated velocity for b[0]
          vFinal[1].x = ((other.m - m) * vTemp[1].x + 2 * m * vTemp[0].x) / (m + other.m);
          vFinal[1].y = vTemp[1].y;
    
          // hack to avoid clumping
          bTemp[0].x += vFinal[0].x;
          bTemp[1].x += vFinal[1].x;
    
          /*** Rotate ball positions and velocities back
           Reverse signs in trig expressions to rotate 
           in the opposite direction */
          // rotate balls
          PVector[] bFinal = { 
            new PVector(), new PVector()
          };
    
          bFinal[0].x = cosine * bTemp[0].x - sine * bTemp[0].y;
          bFinal[0].y = cosine * bTemp[0].y + sine * bTemp[0].x;
          bFinal[1].x = cosine * bTemp[1].x - sine * bTemp[1].y;
          bFinal[1].y = cosine * bTemp[1].y + sine * bTemp[1].x;
    
          // update balls to screen position
          other.position.x = position.x + bFinal[1].x;
          other.position.y = position.y + bFinal[1].y;
    
          position.add(bFinal[0]);
    
          // update velocities
          velocity.x = cosine * vFinal[0].x - sine * vFinal[0].y;
          velocity.y = cosine * vFinal[0].y + sine * vFinal[0].x;
          other.velocity.x = cosine * vFinal[1].x - sine * vFinal[1].y;
          other.velocity.y = cosine * vFinal[1].y + sine * vFinal[1].x;
        }
      }
    
    
      void display() {
        noStroke();
        fill(204);
        ellipse(position.x, position.y, r*2, r*2);
      }
    }
    
  • edited March 2017

    Here is the solution for the previous example... using circles. If you disabled the money line, you will see the undesirable vibration effect.

    EDITED ##############

    Kf

    //REFERENCE: https:// processing.org/examples/circlecollision.html
    
    ArrayList<Ball> balls;
    int n=20;
    int R=30;
    
    void setup() {
      size(640, 360);
      balls = new ArrayList();
    
      //Add new objects but first checking they do not overlap
      for (int i=0; i<n; i++) {
        int ax=0;
        int ay=0;
        int rr=int(random(0.5*R, R));
    
    
       ////EDIT ###########  NEXT block NOT needed anymore
       ////
        ////boolean overlapDetected=true; 
        ////
        //////NEXT "while loop" ensures that the new object to be added to the arraylist 
        //////     doesn't overlap with existing objects in the list
       //// while (overlapDetected==true) {
        ////  overlapDetected=false; //Change to false so for the algorithm to work
        ////  ax=int(random(width));
        ////  ay=int(random(height));       
        ////  for (int j=0; j<i && !overlapDetected; j++) {
         ////   float d=dist(ax, ay, balls.get(j).position.x, balls.get(j).position.y);
         ////   if (d<rr + balls.get(j).r)
         ////     overlapDetected=true;
         //// }
        ////}
    
        //Add object to list
        balls.add(new Ball(ax, ay, rr));
      }
    }
    
    void draw() {
      background(51);
    
      for (int i=0; i<n; i++) {
        Ball b=balls.get(i);
        b.update();
        b.display();
        b.checkBoundaryCollision();
        for (int j=i+1; j<n; j++) {
          balls.get(i).checkCollision(balls.get(j));
        }
      }
    }
    
    
    
    
    
    
    // ############################################
    
    // ############################################
    
    class Ball {
      PVector position;
      PVector velocity;
    
      float r, m;
    
      ArrayList<Ball>  colArray; //Colission array
    
    
    
      Ball(float x, float y, float r_) {
        position = new PVector(x, y);
        velocity = PVector.random2D();
        velocity.mult(3);
        r = r_;
        m = r*.1;
        colArray=new ArrayList<Ball>();
      }
    
      void update() {
        position.add(velocity);
      }
    
      void checkBoundaryCollision() {
        if (position.x > width-r) {
          position.x = width-r;
          velocity.x *= -1;
        } else if (position.x < r) {
          position.x = r;
          velocity.x *= -1;
        } else if (position.y > height-r) {
          position.y = height-r;
          velocity.y *= -1;
        } else if (position.y < r) {
          position.y = r;
          velocity.y *= -1;
        }
      }
    
      void checkCollision(Ball other) {
    
        // get distances between the balls components
        PVector bVect = PVector.sub(other.position, position);
    
        // calculate magnitude of the vector separating the balls
        float bVectMag = bVect.mag();
    
        boolean found=false;
        for (Ball cb : colArray)
          if (cb==other) 
            found=true;
    
        //Now if found is true, two cases:
        //1. If overlap is still going, then do not run overlap correction algorithm. Simply return
        //2. If no overlap anymore, then remove object from list
    
        if (bVectMag > r + other.r) {    
          if (found==true) {
              colArray.remove(other);  //REF: http://docs.oracle.com/javase/1.5.0/docs/api/java/util/ArrayList.html
          }
        } else {
    
          if (found==true) return;  //// MONEY LINE MONEY LINE MONEY LINE
    
          colArray.add(other);
          // get angle of bVect
          float theta  = bVect.heading();
          // precalculate trig values
          float sine = sin(theta);
          float cosine = cos(theta);
    
          /**** bTemp will hold rotated ball positions. You 
           just need to worry about bTemp[1] position*/
          PVector[] bTemp = {
            new PVector(), new PVector()
          };
    
          /**** this ball's position is relative to the other
           so you can use the vector between them (bVect) as the 
           reference point in the rotation expressions.
           bTemp[0].position.x and bTemp[0].position.y will initialize
           automatically to 0.0, which is what you want
           since b[1] will rotate around b[0] */
          bTemp[1].x  = cosine * bVect.x + sine * bVect.y;
          bTemp[1].y  = cosine * bVect.y - sine * bVect.x;
    
          // rotate Temporary velocities
          PVector[] vTemp = {
            new PVector(), new PVector()
          };
    
          vTemp[0].x  = cosine * velocity.x + sine * velocity.y;
          vTemp[0].y  = cosine * velocity.y - sine * velocity.x;
          vTemp[1].x  = cosine * other.velocity.x + sine * other.velocity.y;
          vTemp[1].y  = cosine * other.velocity.y - sine * other.velocity.x;
    
          /**** Now that velocities are rotated, you can use 1D
           conservation of momentum equations to calculate 
           the final velocity along the x-axis. */
          PVector[] vFinal = {  
            new PVector(), new PVector()
          };
    
          // final rotated velocity for b[0]
          vFinal[0].x = ((m - other.m) * vTemp[0].x + 2 * other.m * vTemp[1].x) / (m + other.m);
          vFinal[0].y = vTemp[0].y;
    
          // final rotated velocity for b[0]
          vFinal[1].x = ((other.m - m) * vTemp[1].x + 2 * m * vTemp[0].x) / (m + other.m);
          vFinal[1].y = vTemp[1].y;
    
          // hack to avoid clumping
          bTemp[0].x += vFinal[0].x;
          bTemp[1].x += vFinal[1].x;
    
          /**** Rotate ball positions and velocities back
           Reverse signs in trig expressions to rotate 
           in the opposite direction */
          // rotate balls
          PVector[] bFinal = { 
            new PVector(), new PVector()
          };
    
          bFinal[0].x = cosine * bTemp[0].x - sine * bTemp[0].y;
          bFinal[0].y = cosine * bTemp[0].y + sine * bTemp[0].x;
          bFinal[1].x = cosine * bTemp[1].x - sine * bTemp[1].y;
          bFinal[1].y = cosine * bTemp[1].y + sine * bTemp[1].x;
    
          // update balls to screen position
          other.position.x = position.x + bFinal[1].x;
          other.position.y = position.y + bFinal[1].y;
    
          position.add(bFinal[0]);
    
          // update velocities
          velocity.x = cosine * vFinal[0].x - sine * vFinal[0].y;
          velocity.y = cosine * vFinal[0].y + sine * vFinal[0].x;
          other.velocity.x = cosine * vFinal[1].x - sine * vFinal[1].y;
          other.velocity.y = cosine * vFinal[1].y + sine * vFinal[1].x;
        }
      }
    
    
      void display() {
        noStroke();
        fill(204);
        ellipse(position.x, position.y, r*2, r*2);
      }
    }
    
Sign In or Register to comment.