Colliding particles and the power of abstraction

posted by on 2014.08.17, under Processing
17:

This code is a nice playground for some advanced aspects of Object Oriented Programming (OOP): it is simple enough to be interesting, and show the power and elegance of a more abstract approach to programming. What does the code do? It generates a number of bouncing particles which disappear when colliding with each other. Pretty simple. Now, in general, when you have to test a condition for an object against the totality of objects present, you may run soon in annoying cycles which can obscure the code itself, making it less readable, and which would be best avoided. To do this, I need to introduce one easy, but extremely important observation on how function in Processing (and Java, and many other languages) work: “Objects to a function are passed by reference“. Remember this, write it somewhere, do whatever but don’t forget it. “Oookay”, you say, “but, what does “passed by reference” even mean? What’s a “reference”?”. In OOP, objects are instantiated as initialized references, which means the following: suppose we have a class Ball (with empty constructor), and the following piece of code

Ball p;
p = new Ball();

In plain English, the code above says: create a label p which labels an object of type Ball, then create an object of type Ball, and attach to it the label p. In other words, p is *not* the object*, but it is just a label “referencing” the actual Ball object. Indeed, if we add the following piece of code

Ball q;
q = p;

we are asking to create a new label q, referencing to the same object p is referencing. Now, as soon as we modify the actual object which is referenced, these modifications will reflect on the behaviour of p and q. For instance, suppose that the class Ball has a field a, of type integer, which is initialized to 10. Then the following code

println(q.a);
p.a = 15;
println(q.a);

will return 10, and then 15. In other words, changing p has changed *also* q, and the reason is that we are really not changing p, but rather the object p references to. A bit confusing, right? Neverthless I bet you have seen this behaviour before, if you have ever used (and who hasn’t) arrays.
Indeed, this piece of code would give the same results;

int[] p = new int[10];
int[] q;
q = p;
q[0] = 10;
p[0] = 15;
println(q[0]);

Okay, we are ready to talk about “passing by reference”**. Suppose you have a function

void f(Ball b){
   do something;
}

and suppose we evaluate it on p, as in f(p). What happens is that b becomes a reference to the object referenced by p (as in b = p). So, if you modify b inside the scope of f, then you are really modifying the referenced object, and hence also p. Of course, the same is true for arrays, so be careful with what you do inside the scope of a function, when arrays are involved.
Supposing this is more or less, clear, the question remains: why caring about this for some stupid balls? Well, this simple observation allows to define something like this

class Ball{
int a;
Ball[] balls;

  Ball(int _a, Ball[] _balls){
   a  = 10;
   balls = _balls;
 }
}

Wait, what’s going on here? Am I defining a class Ball in which one of its fields is an array of objects of type… Ball?! Like, isn’t there gonna be a nasty recursion type of end of the universe OMG!!
Calm down, nothing’s bad gonna happen. And the reason lies in the simple observations above: objects variable (and arrays) are defined as reference, and they need to be initialized. In the case above, we are asking for a label balls which is a reference to an array of type Ball: at this point we are *not* calling the constructor Ball( ). When it will be called, though, the array balls will reference to the same array referenced by the label (or variable) _balls. Here we are using that objects are passed to functions by reference (the constructor, in this case). Nice, uh? :)
Notice that, on the other hand, this piece of code

class Ball{
int a;
Ball p;

  Ball(int _a){
   a  = 10;
   p = new Ball();
 }
}

will give a nasty recursion, since we are indeed calling the constructor inside itself.
Ok, still, what do we do with this?

Here’s the Processing code

int num = 50;
ArrayList<Particle> partls;

void setup(){
  size(600, 600);
  background(0);
  partls = new ArrayList<Particle>();

  for (int i = 0; i < num; i++){
    partls.add(new Particle(random(0, width), random(0, height), partls));
  }
}

void draw(){
  ArrayList<Particle> remove = new ArrayList<Particle>();
  background(0);
   
   for (Particle par: partls){
    par.update();
    par.display();
   }
 
   for (Particle par: partls){
    if (par.collide()) remove.add(par);
   }
   
   for (Particle par: remove){
    partls.remove(par);
   }
}

///Define the class Particle

class Particle{
  float x, y, vx, vy, r;
  ArrayList<Particle> others;
 
Particle(float _x, float _y, ArrayList<Particle> _others){
  x = _x;
  y = _y;
  vx = random(-5, 5);
  vy = random(-5, 5);
  others = _others;
  r = 20;
}  

void update(){
  if ( x <=0 || x >= width) vx = -vx;
  if ( y <=0 || y >= height) vy = -vy;
 
  x = (x + vx);
  y = (y + vy);
}

void display(){
  fill(255, 200);
  ellipse(x, y, r, r);
}

boolean collide(){
  boolean b = false;
   for (Particle p: others){
    if (dist(x, y, p.x, p.y) <= r & dist(x, y, p.x, p.y) > 0.0){
     b = true;
     }
   }
   return b;
 }
 
}

Let’s look at the Particle class. Among other fields, we have others, an ArrayList of type Particle, which refers to the ArrayList _others passed to the constructor. The variable others is used in the method collide, which tests the distance between the instantiated object and all the particles in the list others, and returns the value true if a collision happens (the condition dist(x, y, p.x, p.y) > 0.0 ensures no self-collision). Also, notice the “enhanced” for loop, which is pretty elegant.
Now, the magic appears in the main code, namely in

partls = new ArrayList();

for (int i = 0; i < num; i++){ partls.add(new Particle(random(0, width), random(0, height), partls)); } The first line initializes the variable partls as an (empty) ArrayList of type Particle. In the for loop, we add to partls some objects of type Particle by passing to the constructor the required fields: x, y, and others. Since objects are passed by reference to the constructor, this means that, at the end of the for loop, for each object instance of Particle the field others will reference to the same ArrayList referenced by partls, which, by construction, is an ArrayList in which each element is exactly the object Particle created in the for loop. The reference partls gets modified at each iteration of the cycle, and since we are passing it by reference, this modification will reflect also on the variable others. Pretty cool, uh? This is quite a convenient way to manage collisions. Indeed, in the draw() function we have first the usual (enhanced) for loop which updates, and displays the particles. Then we check for collisions: if the particle collides with any other particle, we add it to a remove list. We need to do this, instead of removing it on the spot, because otherwise only one of the two particles involved in a collision would get removed. Finally, we have another for loop, in order to dispose of the colliding particles. Everything is pretty elegant and compact, and most importantly, the code is more readable this way! :)
No video this time, since it’s pretty visually uninteresting.

*Not 100% true, but pretty close.
**There exists also the notion of “passing by value”.

comment

Nice example of trading a little inefficiency for clarity.

I’m reading through your website recent->oldest and would like to thank you for sharing your knowledge and ideas.

Tom ( 10/07/2018 at 5:23 pm )

    Thanks for the feedback!

    me ( 11/07/2018 at 10:02 am )

Please Leave a Reply

Current ye@r *

TrackBack URL :

pagetop