Help a newbie with polymorphic particles

Hi there,

I am working on a university project with processing. This is a first time for me, so bear with me… We are creating a particle system (of sorts) that will eventually trigger sounds etc. The code I have attached below is stripped back to zoom in on the encountered problem and to ensure it doesn’t rely on external files.

The problem is that we have 2 classes of particles (named a larger blue ‘ball’ and smaller pink ‘blip’). Both collide with particles of the same type, but not with each other. We would like them to also collide with one another. in my online research there are potentially 2 ways to do this:

  1. Create a parent class ‘particle’ of which particle types ‘ball’ and ‘blip’ extend this class

  2. Somehow recall the value of coordinates of one class of particle, and use them to define a collide function

I have tried both approaches cant seem to get either of the ground. Some pointers or code would be greatly appreciated!

Thanks a lot!
James

int numBalls = 4;
Ball[] balls = new Ball[numBalls];

int numBlips = 15;
Blip[] blips = new Blip[numBlips];

float spring = 1.6;
float gravity = 0.001;
float friction = -0.8;

void setup() {
  frameRate(80);
  
  size(displayWidth, displayHeight, OPENGL, P2D);


  for (int i = 0; i < numBalls; i++) 
    balls[i] = new Ball(random(300, 600), random(450, 650), (80), i, balls);

  for (int a = 0; a < numBlips; a++) 
    blips[a] = new Blip(random(300, 600), random(450, 650), (10), a, blips);

    smooth();
  }

  int index = 0;

  void draw() {
  background(0, 10);

    for (Ball ball : balls) {
      ball.collide();
      ball.move();
      ball.display();
    }

    for (Blip blip : blips) {
      blip.collide();
      blip.move();
      blip.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*1.1;
          float ay = (targetY - others[i].y) * spring*1.1;
          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;
        //sound[index].trigger();
      } 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() {
      float disc=40; 
      gradientdisc( 
        x, 
        y, 
        40, 
        disc, 
        color(0, 0, 255), 
        color(255, 255, 255) 
        ); 
      noStroke();
    }
    void gradientdisc( float x, float y, float radiusX, float radiusY, int fromC, int toC )
    { 
      noStroke(); 
      beginShape(TRIANGLE_STRIP);
      int halfC = lerpColor(fromC, toC, 0.4);

      for (float theta=0; theta<TWO_PI; theta+=TWO_PI/36)
      { 
        fill(halfC);  
        vertex(x, y);
        if ( theta <= PI )
          fill(lerpColor(fromC, toC, (theta%PI)/PI ));
        else
          fill(lerpColor(toC, fromC, (theta%PI)/PI ));
        vertex(x+radiusX*cos(theta), y+radiusY*sin(theta));
      } 
      endShape();
      float disc2=50; 
      stroke(0, 10);
      strokeWeight(1);
      noFill();
      ellipse(x, y, 100, disc2*2);
    }
  }

  class Blip {

    float x, y;
    float diameter;
    float vx = 0;
    float vy = 0;
    int id;
    Blip[] others;

    Blip(float xin, float yin, float din, int idin, Blip[] oin) {
      x = xin;
      y = yin;
      diameter = din;
      id = idin;
      others = oin;
    } 

    void collide() {
      for (int i = id + 1; i < numBlips; 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 * 1.2;
          float ay = (targetY - others[i].y) * spring * 1.2;
          vx -= ax;
          vy -= ay;
          others[i].vx += ax;
          others[i].vy += ay;
        }
      }
    }
    void move() {
      vy += gravity * 2;
      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() {
      stroke(255, 0, 144);
      strokeWeight(1);
      noFill();
      ellipse(x, y, diameter, diameter);
    }
  }
1 Like

Hi jbr123,

It is really inefficient to keep an array of all the other object created inside your object.

Imagine that you have 1000 objects. it means that you have to get in memory 1000! objects. It is not completely true since it does not perform deep copy of the object but just point towards them but still.

The way to do what you what is to modify your collide() method with a collideWith(Ball other) method. There is not much to change, but it allow you to get rid of your others array since now you can give the other that you want to check. You can even pass on the complete array of ball if you want with something like this: collideWith(Ball[] others) but I advise the first method since it allows a bit more flexibility in case you just want to collide on specific object.

Then, if you don’t want to create a parent class you need to also create a collideWith(Blip other) method that will this time take a Blip object.

Do the same for the Blip class.

Then do this in your draw() function

for (int i = 0; i < numBalls; i++) {
  for (int j = i; j < numBalls; j++) {
    Balls[i].collidewith(Balls[j]);
  }

  for (int j = 0; j < numBlips; j++) {
    Balls[i].collidewith(Blip[j]);
  }
}
1 Like

Hi jb4x,

Thank you so much for your reply!

As I said, Im completely new to processing. As a result, some of the lingo goes right over my head… What do you mean exactly when you say I can ’ get rid of your others array since now you can give the other that you want to check.’

And am i just updating the collide function, or creating a new collideWith function?

In you Ball class, you have a Ball[] others variable that you don’t want in there.

In you old collide() method, you were using that variable to check the collision with all the other ball previously created.

But since you want to delete it, you need a new way to get the information of the other ball. This is done by giving a Ball as an argument to the collide function. That’s what’s in the curly bracket in this function: collideWith(Ball other)

This way you don’t need that Ball[] others variable since you are giving the function the needed information through the argument.

The function is almost the same. Just get rid of your loop since you don’t have your Ball[] others array and replace the others[i] by other[i] (wich is the name of the argument of the new function). I just took the liberty to rename it collideWith to give you a bit more understanding of what the function was doing.

Hi jb4x,

I have spent a couple of hours trying to work out where I am going wrong here. I have pasted my code below, in what respect am i not following your pointers? I understand the theory of what you have written above, but am stuggling to put it into practice!

int numBalls = 4;
Ball[] balls = new Ball[numBalls];

int numBlips = 15;
Blip[] blips = new Blip[numBlips];

float spring = 1.6;
float gravity = 0.001;
float friction = -0.8;

void setup() {
  frameRate(80);

  size(displayWidth, displayHeight, OPENGL, P2D);


  for (int i = 0; i < numBalls; i++) 
    balls[i] = new Ball(random(300, 600), random(450, 650), (80), i, balls);

  for (int a = 0; a < numBlips; a++) 
    blips[a] = new Blip(random(300, 600), random(450, 650), (10), a, blips);

  smooth();
}

int index = 0;

void draw() {
  background(0, 10);

  for (Ball ball : balls) {
    ball.collide();
    ball.move();
    ball.display();
  }

  for (Blip blip : blips) {
    blip.collide();
    blip.move();
    blip.display();
  }

  for (int i = 0; i < numBalls; i++) {
    for (int j = i; j < numBalls; j++) {
      Balls[i].collide(Balls[j]);
    }

    for (int j = 0; j < numBlips; j++) {
      Balls[i].collide(Blip[j]);
    }
  }
}
class Ball {

  float x, y;
  float diameter;
  float vx = 0;
  float vy = 0;
  int id;
  Ball[] other;


  Ball(float xin, float yin, float din, int idin, Ball[] oin) {
    x = xin;
    y = yin;
    diameter = din;
    id = idin;
    other = oin;
  }    

  void collide() {
    for (int i = id + 1; i < numBalls; i++) {
      float dx = other[i].x - x;
      float dy = other[i].y - y;
      float distance = sqrt(dx*dx + dy*dy);
      float minDist = other[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 - other[i].x) * spring*1.1;
        float ay = (targetY - other[i].y) * spring*1.1;
        vx -= ax;
        vy -= ay;
        other[i].vx += ax;
        other[i].vy += ay;
      }
      collideWith(blip other);
    }
  }
  void move() {
    vy += gravity;
    x += vx;
    y += vy;
    if (x + diameter/2 > width) {
      x = width - diameter/2;
      vx *= friction;
      //sound[index].trigger();
    } 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() {
    float disc=40; 
    gradientdisc( 
      x, 
      y, 
      40, 
      disc, 
      color(0, 0, 255), 
      color(255, 255, 255) 
      ); 
    noStroke();
  }
  void gradientdisc( float x, float y, float radiusX, float radiusY, int fromC, int toC )
  { 
    noStroke(); 
    beginShape(TRIANGLE_STRIP);
    int halfC = lerpColor(fromC, toC, 0.4);

    for (float theta=0; theta<TWO_PI; theta+=TWO_PI/36)
    { 
      fill(halfC);  
      vertex(x, y);
      if ( theta <= PI )
        fill(lerpColor(fromC, toC, (theta%PI)/PI ));
      else
        fill(lerpColor(toC, fromC, (theta%PI)/PI ));
      vertex(x+radiusX*cos(theta), y+radiusY*sin(theta));
    } 
    endShape();
    float disc2=50; 
    stroke(0, 10);
    strokeWeight(1);
    noFill();
    ellipse(x, y, 100, disc2*2);
  }
}

class Blip {

  float x, y;
  float diameter;
  float vx = 0;
  float vy = 0;
  int id;
  Blip[] other;

  Blip(float xin, float yin, float din, int idin, Blip[] oin) {
    x = xin;
    y = yin;
    diameter = din;
    id = idin;
    other = oin;
  } 

  void collide() {
    for (int i = id + 1; i < numBlips; i++) {
      float dx = other[i].x - x;
      float dy = other[i].y - y;
      float distance = sqrt(dx*dx + dy*dy);
      float minDist = other[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 - other[i].x) * spring * 1.2;
        float ay = (targetY - other[i].y) * spring * 1.2;
        vx -= ax;
        vy -= ay;
        other[i].vx += ax;
        other[i].vy += ay;
      }
      collideWith(ball other);
    }
  }
  void move() {
    vy += gravity * 2;
    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() {
    stroke(255, 0, 144);
    strokeWeight(1);
    noFill();
    ellipse(x, y, diameter, diameter);
  }
}```

The main problem still persists: Your Ball calls has an ARRAY OF BALLS in it. So for every Ball in the gloab array, you are storing a copy of that array for each ball in it! Argh!

A Ball object represents ONE ball. There is no need to have an array of them inside your class!

Instead, you need to check pass a Ball to your collide() function. Then the collision is checking between the Ball you are calling collide on, and that other Ball, the one being passed in.

int numBalls = 4;
int numBlips = 15;

Ball[] balls = new Ball[numBalls+numBlips];

float spring = 1.6;
float gravity = 0.001;
float friction = -0.8;

void setup() {
  //frameRate(80); // Sketch will run as fast as it can anyway, usually 60fps. This doesn't increase that, much as you would like.

  size(displayWidth, displayHeight, OPENGL, P2D);

  for (int i = 0; i < numBalls+numBlips; i++) {
    boolean is_a_ball = i < numBalls;
    balls[i] = new Ball(random(300, 600), random(450, 650), ((is_a_ball)?(80):(10)), i, is_a_ball);
    //println( "" + i  + " " + ((is_a_ball)?(10):(80)) ); 
  }

  //smooth(); // Enabled by default.
}

int index = 0;

void draw() {
  background(0, 10);
  // For each ball,
  for (int i = 0; i < balls.length-1; i++) {
    // For all the balls after it,
    for ( int j = i+1; j < balls.length; j++) {
      // Determine if they collide.
      balls[i].collide(balls[j]);
    }
  }
  // Then move and draw all the balls.
  for (int i = 0; i < balls.length-1; i++) {
    balls[i].move();
    balls[i].display();
  }
}

class Ball {

  float x, y;
  float diameter;
  float vx = 0;
  float vy = 0;
  int id;
  boolean is_ball;

  Ball(float xin, float yin, float din, int idin, boolean iis_ball) {
    x = xin;
    y = yin;
    diameter = din;
    id = idin;
    is_ball = iis_ball;
  }    

  void collide(Ball other) {
    float dx = other.x - x;
    float dy = other.y - y;
    float distance = sqrt(dx*dx + dy*dy);
    float minDist = other.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 - other.x) * spring*1.1;
      float ay = (targetY - other.y) * spring*1.1;
      vx -= ax;
      vy -= ay;
      other.vx += ax;
      other.vy += ay;
    }
  }

  void move() {
    vy += gravity * (is_ball?1:2);
    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() {
    if ( is_ball ) {
      display_ball();
    } else { 
      display_blip();
    }
  }
  void display_ball() {
    float disc=40; 
    gradientdisc( 
      x, 
      y, 
      40, 
      disc, 
      color(0, 0, 255), 
      color(255, 255, 255) 
      ); 
    noStroke();
  }

  void gradientdisc( float x, float y, float radiusX, float radiusY, int fromC, int toC ) { 
    noStroke(); 
    beginShape(TRIANGLE_STRIP);
    int halfC = lerpColor(fromC, toC, 0.4);
    for (float theta=0; theta<TWO_PI; theta+=TWO_PI/36) { 
      fill(halfC);  
      vertex(x, y);
      if ( theta <= PI ) {
        fill(lerpColor(fromC, toC, (theta%PI)/PI ));
      } else {
        fill(lerpColor(toC, fromC, (theta%PI)/PI ));
      }
      vertex(x+radiusX*cos(theta), y+radiusY*sin(theta));
    } 
    endShape();
    float disc2=50; 
    stroke(0, 10);
    strokeWeight(1);
    noFill();
    ellipse(x, y, 100, disc2*2);
  }

  void display_blip() {
    stroke(255, 0, 144);
    strokeWeight(1);
    noFill();
    ellipse(x, y, diameter, diameter);
  }
}

Anyway, I merged the Blips with the Balls. Notice that the Ball class now has a is_ball boolean that knows if a given ball is a blip or a ball.

Also notice that there is ONLY ONE ARRAY OF OBJECTS: balls. Each single ball does not have a copy of this array!

And the collides() function is being passed a ball to check for collision against.

1 Like