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.

No comments:

Post a Comment