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!

Tuesday, July 7, 2015

Sprite dynamics using Vector calculation


I recently stumbled upon the excellent book The Nature of Code by Daniel Shiffman. It's one of the best books I've read so far when it comes starting with learing about motion and physics in games. Not only do you get a very good written lecture, but Daniel also made a video series to supplement the book. And it can be read on the web for free. That book was an instant buy.

The code is written in Processing, but that can be easily translated to JavaFX.

I leave the details to the book and only describe here how the code is translated to JavaFX. One thing to notice is that the motion and physics are based on vector calculations. The JavaFX Point2D class provides us the means to work with, hower in the end it won't suffice, it does for now. The main reason is that the Point2D class can't be modified, you'll always get new instances of it when you add, subtract, multiply, etc. That's not very performing. As a replacement you could write your own vector class or use the PVector class from Processing, since it's written in Java (please note the license of Processing).

The code example is from chapter 2.10 Everything Attracts Everything. What we need is a sprite, i. e. a Mover class with attributes like

  • location
  • velocity
  • acceleration
  • mass

For sake of simplicity the Mover is a Circle. From the book we learn that the movement consists of 3 steps:

  • apply force(s) and get the acceleration
  • apply acceleration to velocity
  • apply velocity to location

This is basically the required code, what it does mathematically is very well explained in the book:

Point2D location;
Point2D velocity;
Point2D acceleration;
...

public void applyForce(Point2D force) {

  Point2D f = new Point2D( force.getX(), force.getY());
  f = f.multiply(1/mass);
  
  acceleration = acceleration.add(f);
}

public void move() {

  // set velocity depending on acceleration
  velocity = velocity.add(acceleration);

  // limit velocity to max speed
  double mag = velocity.magnitude();
  if( mag > Settings.MOVER_MAX_SPEED) {
   velocity = velocity.normalize();
   velocity = velocity.multiply(mag);
  }

  // change location depending on velocity
  location = location.add(velocity);

  // clear acceleration
  acceleration = new Point2D(0,0);
}

public Point2D attract(Mover m) {

  // force direction
  Point2D force = location.subtract(m.location);
  double distance = force.magnitude();
  
  // constrain movement
  distance = constrain(distance, Settings.ATTRACTION_DISTANCE_MIN, Settings.ATTRACTION_DISTANCE_MAX);
  
  force = force.normalize();

  // force magnitude
  double strength = (Settings.GRAVITATIONAL_CONSTANT * mass * m.mass) / (distance * distance);
  force = force.multiply(strength);

  return force;
}

public void display() {
  setCenterX( location.getX());
  setCenterY( location.getY());
}

And then we create a game loop in which we apply the forces, move the sprites and update them in the UI:

gameLoop = new AnimationTimer() {

 @Override
 public void handle(long now) {

  // force: attraction
  for (Mover m1 : allMovers) {
   for (Mover m2 : allMovers) {

    if (m1 == m2)
     continue;

    // calculate attraction
    Point2D force = m1.attract(m2);

    // apply attraction
    m2.applyForce(force);

   };
  };

  // move
  allMovers.forEach(Mover::move);

  // update in fx scene
  allMovers.forEach(Mover::display);

 }
};

gameLoop.start();


That's it, that's the core, nothing much to it. You can as easily apply other forces like wind and gravity, as described in the book.

Just in case your pc isn't powerful enough to handle this many nodes, or just for the sake of quick performance improvement, you may want to use a parallel stream() in order to apply the force like this:

    allMovers.stream().parallel().forEach(m1 -> {
     allMovers.stream().parallel().forEach(m2 -> {

      if (m1 != m2) {

       // calculate attraction
       Point3D force = m1.attract(m2);
 
       // apply attraction
       m2.applyForce(force);

      }
     });
    });


Here's a video about how it looks like. Random movers are placed on the screen, everything attracts everything:


Interested ones can get the full JavaFX source from this gist.