Sunday, July 26, 2015

Particle Systems

Reading the Particle Systems chapter in the excellent book The Nature of Code by Daniel Shiffman I had to implement a system to toy around. It's in fact very easy to come up with something like this:



Here's a summary of what's necessary to create a particle system in JavaFX.

Shiffman's particle classes consist of properties for

  • acceleration vector
  • velocity vector
  • location vector

The acceleration is accumulated depending on the various forces in the system, then it is applied to the velocity and the velocity is applied to the location. Here's a short snippet:
 Vector2D location;
 Vector2D velocity;
 Vector2D acceleration;
 ...

  /**
   * Accumulate forces. Here we could also consider mass.
   */ 
 public void applyForce(Vector2D force) {

  acceleration.add(force);
      
 }

 /**
  * Standard movement method: calculate valocity depending on 
  * accumulated acceleration force, then calculate the location.
  * Reset acceleration so that it can be recalculated in the 
  * next animation step.
  */
 public void move() {
  
  // set velocity depending on acceleration
  velocity.add(acceleration);
     
  // limit velocity to max speed
  velocity.limit(maxSpeed);
     
  // change location depending on velocity
  location.add(velocity);

  // angle: towards velocity (ie target)
  angle = velocity.angle();
     
  // clear acceleration
  acceleration.multiply(0);
  
 }
 
 /**
  * Update node position
  */
 public void display() {
  
  // location
  relocate(location.x - centerX, location.y - centerY);
  
  // rotation
  setRotate(Math.toDegrees( angle));

 } 
Too easy to be true? Well, it is. The Vector2D class is a custom implementation. You could in theory use Point2D of JavaFX, but that class always returns a new Point2D class whereas in the custom implementation you can modify the x and y coordinates instead of spawning a new object which of course would cost performance, especially with thousands of particles on the screen.

Now we need an AnimationTimer in which we invoke these methods. Here's a snippet of how it could look like:
  List<Particle> allParticles = new ArrayList<>();
    
  ...
    
  Vector2D forceGravity = new Vector2D( 0, 0.1);
    
  AnimationTimer animationLoop = new AnimationTimer() {
   
   @Override
   public void handle(long now) {

    // add a new particle each frame
    addParticle();

    // apply force: gravity
    allParticles.forEach(sprite -> {
     sprite.applyForce(forceGravity);
    });

    // move sprite: apply acceleration, calculate velocity and location
    allParticles.stream().parallel().forEach(Sprite::move);

    // change particle node location on screen
    allParticles.stream().parallel().forEach(Sprite::display);
    
    // life span of particle
    allParticles.stream().parallel().forEach(Sprite::decreaseLifeSpan);

    // remove all particles that aren't visible anymore
    removeDeadParticles();

   }
  };

  animationLoop.start();
In this version it is assumed that the particles are all of type Node (e. g. a Circle, Rectangle, ImageView, etc). That was actually the first version I created.

However, when I implemented this, I noticed that the limit on my system is around 2.000 particles per frame. That number seemed a little bit low. So I toyed around and adapted the display mechanism in order to paint the particles on a Canvas instead of a node. In other words: The way you usually do this kind of stuff.

It was just a few lines of code changes. First the images and their colors are precalculated. For a particle lifespan of 256 this of course results in 256 precalculated images. Then the image is picked depending on the particle's lifespan and drawn on the canvas:
 // draw all particles on canvas
 // -----------------------------------------
 graphicsContext.setFill(Color.BLACK);
 graphicsContext.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());

 double particleSizeHalf = Settings.get().getParticleWidth() / 2;
 allParticles.stream().forEach(particle -> {

  Image img = images[particle.getLifeSpan()];
  graphicsContext.drawImage(img, particle.getLocation().x - particleSizeHalf, particle.getLocation().y - particleSizeHalf);

 });
Those few lines of code changes gave it a boost of a factor of 10 - 15. By using this technique, keeping the particle code as it is and invoking the canvas painting instead of the display method the system currently runs at 28.000 particles per frame with 60 fps in full-hd resolution. It's actually a lot of fun to toy around.

Here's a demonstration video:



In the video you can see different forces like attractor, repeller and gravity working together. You can change parameters via user interface. New repellers and attractors can be added via context menu.

Interested ones can get the code from this gist.

Have fun & keep on coding!

3 comments:

  1. Hi. Sorry for off topic comment, but I saw your stack overflow post about graph in JavaFX, http://stackoverflow.com/a/30696075/5488597 What license is the code? Is it somewhere on github so I can fork it and build on it? I would like to use it as starting point for some graphing I need to do.

    Thanks in advance

    A

    ReplyDelete
    Replies
    1. There is no license on that code. Have fun with it and keep on coding! :-)

      Delete