Balls bouncing off each other, without using Box2D or other libraries

This is my code:

Circle[] circles = {
  new Circle(40,color(0),color(255,0,0),0,2,0,0.5,new PVector(250,300),new PVector(0,0)),
  new Circle(200,color(0),color(255,0,0),0,10,0,0.5,new PVector(500,300),new PVector(0,0)),
  new Circle(20,color(0),color(255,0,0),0,0.1,0,0.5,new PVector(750,300),new PVector(0,0)),
};

PVector wind = (PVector.random2D()).div(20);
PVector gravity = new PVector(0,0.2);

void setup(){
  size(1000,600);
}

void draw(){
  background(0);
  circles[0].applyForce(wind);
  circles[0].applyForce(gravity);
  circles[0].update();
  circles[1].applyForce(wind);
  circles[1].applyForce(gravity);
  circles[1].update();
  circles[2].applyForce(wind);
  circles[2].applyForce(gravity);
  circles[2].update();
}

class Circle{
  float radius;
  color colorStroke;
  color colorFill;
  float thickness;
  
  float mass;
  float friction;
  float restitution;
  
  PVector position,velocity;
  
  Circle(float r,color cs,color cf,float t,float m,float f,float re,PVector p,PVector v){
    radius = r;
    colorStroke = cs;
    colorFill = cf;
    thickness = t;
    
    mass = m;
    friction = f;
    restitution = re;
    
    position = p;
    velocity = v;
  }
  
  void update(){
    if(thickness == 0){
      noStroke();
    }else{
      strokeWeight(thickness);
    }
    stroke(colorStroke);
    fill(colorFill);
    ellipse(position.x,position.y,radius,radius);
    
    position.add(velocity);
    
    if(position.y > (height - (radius / 2)) && velocity.y > 0){
      velocity.y = velocity.y * -restitution;
      position.y = height - (radius / 2);
    }
    if(position.y < (radius / 2) && velocity.y < 0){
      velocity.y = velocity.y * -restitution;
      position.y = radius / 2;
    }
    if(position.x > (width - (radius / 2)) && velocity.x > 0){
      velocity.x = velocity.x * -restitution;
      position.x = width - (radius / 2);
    }
    if(position.x < (radius / 2) && velocity.x < 0){
      velocity.x = velocity.x * -restitution;
      position.x = radius / 2;
    }
  }
  
  void applyForce(PVector force){
      velocity.x = velocity.x + (force.x / mass);
      velocity.y = velocity.y + (force.y);
  }
}

The edge bouncing is finished, but I want the balls to bounce off each other. So how do I make them detect collision and bounce?

1 Like

Please help :confused: :confused: :confused: :no_mouth:

Writing “collision detection” (or just “collision det”) in the search box above produce some hits.

Here’s one which might be of interest: Collision Detection (Game Design)

1 Like

from the examples section in processing

/**
 * Bouncy Bubbles  
 * based on code from Keith Peters. 
 * 
 * Multiple-object collision.
 */
 
 
int numBalls = 12;
float spring = 0.05;
float gravity = 0.03;
float friction = -0.9;
Ball[] balls = new Ball[numBalls];

void setup() {
  size(640, 360);
  for (int i = 0; i < numBalls; i++) {
    balls[i] = new Ball(random(width), random(height), random(30, 70), i, balls);
  }
  noStroke();
  fill(255, 204);
}

void draw() {
  background(0);
  for (Ball ball : balls) {
    ball.collide();
    ball.move();
    ball.display();  
  }
}

class Ball {
  
  float x, y;
  float diameter;
  float vx = 0;
  float vy = 0;
  int id;
  Ball[] others;
 
  Ball(float xin, float yin, float din, int idin, Ball[] oin) {
    x = xin;
    y = yin;
    diameter = din;
    id = idin;
    others = oin;
  } 
  
  void collide() {
    for (int i = id + 1; i < numBalls; i++) {
      float dx = others[i].x - x;
      float dy = others[i].y - y;
      float distance = sqrt(dx*dx + dy*dy);
      float minDist = others[i].diameter/2 + diameter/2;
      if (distance < minDist) { 
        float angle = atan2(dy, dx);
        float targetX = x + cos(angle) * minDist;
        float targetY = y + sin(angle) * minDist;
        float ax = (targetX - others[i].x) * spring;
        float ay = (targetY - others[i].y) * spring;
        vx -= ax;
        vy -= ay;
        others[i].vx += ax;
        others[i].vy += ay;
      }
    }   
  }
  
  void move() {
    vy += gravity;
    x += vx;
    y += vy;
    if (x + diameter/2 > width) {
      x = width - diameter/2;
      vx *= friction; 
    }
    else if (x - diameter/2 < 0) {
      x = diameter/2;
      vx *= friction;
    }
    if (y + diameter/2 > height) {
      y = height - diameter/2;
      vy *= friction; 
    } 
    else if (y - diameter/2 < 0) {
      y = diameter/2;
      vy *= friction;
    }
  }
  
  void display() {
    ellipse(x, y, diameter, diameter);
  }
}
1 Like

What is the array “other” for?

And also the float “spring” ?

not sure as I’ve not had time to study it you might have to do a bit of research.

1 Like

Alright. Can you tell me where you got this script (Or you wrote it yourself)?

open the examples in your processing ide, the application includes a ton of examples.

1 Like

It’s the same as the “balls” array. If you look in the collide() method, you’ll see that it’s used for getting the position of the other “balls” objects.

Btw notice how the for loop starts at id + 1, to avoid collision detection twice for the same two objects.

But actually it isn’t needed. You could just use balls[i] instead of others[i] in the collision() method. I guess it was done to make the code more readable.

Click to (un) expand
/**
 * Bouncy Bubbles mod without "others" array 
 * based on code from Keith Peters. 
 * 
 * Multiple-object collision.
 */
 
 
int numBalls = 12;
float spring = 0.05;
float gravity = 0.03;
float friction = -0.9;
Ball[] balls = new Ball[numBalls];

void setup() {
  size(640, 360);
  for (int i = 0; i < numBalls; i++) {
    balls[i] = new Ball(random(width), random(height), random(30, 70), i);
  }
  noStroke();
  fill(255, 204);
}

void draw() {
  background(0);
  for (Ball ball : balls) {
    ball.collide();
    ball.move();
    ball.display();  
  }
}

class Ball {
  
  float x, y;
  float diameter;
  float vx = 0;
  float vy = 0;
  int id;
 
  Ball(float xin, float yin, float din, int idin) {
    x = xin;
    y = yin;
    diameter = din;
    id = idin;
  } 
  
  void collide() {
    for (int i = id + 1; i < numBalls; i++) {
      float dx = balls[i].x - x;
      float dy = balls[i].y - y;
      float distance = sqrt(dx*dx + dy*dy);
      float minDist = balls[i].diameter/2 + diameter/2;
      if (distance < minDist) { 
        float angle = atan2(dy, dx);
        float targetX = x + cos(angle) * minDist;
        float targetY = y + sin(angle) * minDist;
        float ax = (targetX - balls[i].x) * spring;
        float ay = (targetY - balls[i].y) * spring;
        vx -= ax;
        vy -= ay;
        balls[i].vx += ax;
        balls[i].vy += ay;
      }
    }   
  }
  
  void move() {
    vy += gravity;
    x += vx;
    y += vy;
    if (x + diameter/2 > width) {
      x = width - diameter/2;
      vx *= friction; 
    }
    else if (x - diameter/2 < 0) {
      x = diameter/2;
      vx *= friction;
    }
    if (y + diameter/2 > height) {
      y = height - diameter/2;
      vy *= friction; 
    } 
    else if (y - diameter/2 < 0) {
      y = diameter/2;
      vy *= friction;
    }
  }
  
  void display() {
    ellipse(x, y, diameter, diameter);
  }
}

If you play with the value, you’ll see how it affects the collisions. Try a value 10x the original, then 1/10th the original, for example.

Or look in the collision() method.

        float ax = (targetX - others[i].x) * spring;
        float ay = (targetY - others[i].y) * spring;

“spring” is used to modify how much “force” (or rather velocity) is applied at each moment (frame).

It’s essentially just Newton’s 3rd law of motion, the one about applying a force to an object, results in the object applying the same but opposite force back (action - reaction).
Basically swapping the result of the collision “force” between the objects in the next lines.

        vx -= ax;
        vy -= ay;
        others[i].vx += ax;
        others[i].vy += ay;

That’s what I got out of it anyway…

Since I’m about to post, I’ll answer (Btw he did say where he got it, from the Processing IDE’s examples).

You should find it here: File menu > “Examples…” → Topics > Motion > Bouncy bubbles.

1 Like

Thanks :smiley: That explain anything

1 Like

It worked fine!

Code:

float fadeSpeed = 20;

float mouseStrength = 100;
float windStrength = 0.1;
float gravityStrength = 0.2;

Circle[] circles = new Circle[12];

PVector wind = (PVector.random2D()).mult(0.1);
PVector gravity = new PVector(0,gravityStrength);

void setup(){
  background(0);
  size(1000,600);
  for(int n = 0;n < circles.length;n++){
    circles[n] = new Circle(
                            random(20,100),
                            color(random(0,255),random(0,255),random(0,255),random(128,255)),
                            color(random(0,255),random(0,255),random(0,255),random(128,255)),
                            random(0,20),
                            random(-5.0,20.0),
                            random(0.0,1.0),
                            new PVector(random(0,width),random(0,height)),
                            new PVector(random(-0.2,-0.2),random(-0.2,0.2))
                           );
  }
}

void draw(){
  fill(0,fadeSpeed);
  rect(0,0,width,height);
  
  for(int n = 0;n < circles.length;n++){
    circles[n].applyForce(wind);
    circles[n].applyForce(gravity);
    circles[n].update();
    circles[n].display();
  }
  
  drawArrow(new PVector(500,200),80,angle(new PVector(0,0),wind),8,color(0));
  drawArrow(new PVector(500,200),80,angle(new PVector(0,0),wind),5,color(255));
  
  if(mousePressed && mouseButton == CENTER){
    wind = new PVector(mouseX - 500,mouseY - 200);
    wind.normalize();
    wind.div(10);
  }else{
    wind.rotate((noise(frameCount) - 0.5) / 10);
  }
}

void drawArrow(PVector position,float distance,float angle,float thickness,color colorStroke){
  strokeWeight(thickness);
  stroke(colorStroke);
  
  pushMatrix();
  translate(position.x,position.y);
  rotate(angle);
  rotate(-QUARTER_PI);
  translate(distance,distance);
  line(-distance,-distance,0,0);
  rotate(HALF_PI + QUARTER_PI);
  line(0,0,20,20);
  rotate(-(PI + HALF_PI));
  line(0,0,20,20);
  popMatrix();
}

float angle(PVector v1, PVector v2) {
  float a = atan2(v2.y, v2.x) - atan2(v1.y, v1.x);
  if (a < 0) a += TWO_PI;
  return a;
}

class Circle{
  float radius;
  color colorStroke;
  color colorFill;
  float thickness;
  
  float mass;
  float restitution;
  
  PVector position,velocity;
  
  Circle(float r,color cs,color cf,float t,float m,float re,PVector p,PVector v){
    radius = r;
    colorStroke = cs;
    colorFill = cf;
    thickness = t;
    
    mass = m;
    restitution = re;
    
    position = p;
    velocity = v;
  }
  
  void update(){
    position.add(velocity);
    
    if(position.y > (height - (radius / 2)) && velocity.y > 0){
      velocity.y = velocity.y * -restitution;
      position.y = height - (radius / 2);
    }
    if(position.y < (radius / 2) && velocity.y < 0){
      velocity.y = velocity.y * -restitution;
      position.y = radius / 2;
    }
    if(position.x > (width - (radius / 2)) && velocity.x > 0){
      velocity.x = velocity.x * -restitution;
      position.x = width - (radius / 2);
    }
    if(position.x < (radius / 2) && velocity.x < 0){
      velocity.x = velocity.x * -restitution;
      position.x = radius / 2;
    }
    
    for (int i = 1;i < circles.length;i++) {
      float dx = circles[i].position.x - position.x;
      float dy = circles[i].position.y - position.y;
      float distance = sqrt(dx*dx + dy*dy);
      float minDist = ((circles[i].radius) / 2) + (radius / 2);
      if (distance < minDist) { 
        float angle = atan2(dy, dx);
        float targetX = position.x + cos(angle) * minDist;
        float targetY = position.y + sin(angle) * minDist;
        float ax = (targetX - circles[i].position.x);
        float ay = (targetY - circles[i].position.y);
        velocity.x -= ax;
        velocity.y -= ay;
        circles[i].velocity.x += ax;
        circles[i].velocity.y += ay;
      }
    }
  }
  
  void applyForce(PVector force){
    velocity.x = velocity.x + (force.x / abs(mass));
    if(mass < 0){
      velocity.y = velocity.y - force.y;
    }
    if(mass == 0){
      velocity.y = velocity.y;
    }
    if(mass > 0){
      velocity.y = velocity.y + force.y;
    }
  }
  
  void display(){
    stroke(colorStroke);
    if(thickness == 0){
      noStroke();
    }else{
      strokeWeight(thickness);
    }
    
    fill(colorFill);
    ellipse(position.x,position.y,radius,radius);
  }
}

The arrow I drawn is the wind direction.

3 Likes