5

Situation

I created a particle system with JavaFX using the following technique:

Each particle is an ImageView which contains an Image with a radial gradient:

enter image description here

The particle handling loop is an AnimationTimer in which the list of particles is handled via the list's stream().parallel() method, it actually gives the whole system a boost. Something like this:

loop = new AnimationTimer() {

    @Override
    public void handle(long now) {

        addParticle();

        // apply force: gravity
        allParticles.stream().parallel().forEach(Particle::applyForceGravity);

        // move particle
        allParticles.stream().parallel().forEach(Particle::move);

        // update position in fx scene
        allParticles.forEach(Particle::display);

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

    }
};

The particle's color changes via ColorAdjust during its lifecycle. I used fire colors for testing, something like this which contains 1700 particles:

enter image description here

What I've learned:

  • not using parallel() is slower
  • using Circle with transparency is way slower than using an ImageView
  • using a Blend Mode is very slow
  • using a PixelWriter to change the image color is unbearable slow. Question arises: how does ColorAdjust change the color (via d3d & hardware)? I haven't found the mechanism in the JavaFX source code.

Question

Is there a better way to implement a particle system in JavaFX (other Node types, Thread, etc) in regards to performance?

I can post some code if anyone wants to toy around.

Thank you very much for the expertise!


Edit: Since it was asked, you can get the full code which uses nodes as particles with coloradjust from this gist. Btw, even if you pre-render the images and don't use coloradjust, the performance is low.

However, the question is more of a theoretical kind, so digging through the code isn't really necessary.

Roland
  • 18,114
  • 12
  • 62
  • 93
  • Can you please post the particle methods? Maybe you have seen this article about AnimationTimer? http://blog.netopyr.com/2012/06/14/using-the-javafx-animationtimer/ – aw-think Jul 24 '15 at 16:00
  • The `KineticModel` cited [here](http://stackoverflow.com/a/7425460/230513) also uses a custom, translucent `RadialGradientPaint`, but it pre-computes the required images; you might able able to create a suitable gamut using `javafx.scene.paint.Color.hsb()`. – trashgod Jul 24 '15 at 16:21
  • @NwDX: yes, I know that page, thanks. That's more or less how I did it. I'll post the code tomorrow, I have to reduce it to a minimum first. – Roland Jul 24 '15 at 16:22
  • @trashgod: Thanks, I just toyed around with precalculating the particle images and using a Canvas instead of Nodes. I didn't expect it to be that fast, but I'm currently at more than 12000 particles with clearing the canvas and drawing the precalculated images over and over. – Roland Jul 24 '15 at 16:24

2 Answers2

7

I think I can add an answer to my own question. But I hope someone else with more experience can share their knowledge, because what I came up with was just the result of toying around:

Painting on a Canvas instead of using JavaFX ImageView nodes surprisingly resulted in a factor of at least 10x speed increase. This is basically what I did:

  • pre-calculate the images with the gradients, i. e. the color and the size of them depending on the particle's lifespan
  • in the animation timer get the pre-calculated image and paint it on the canvas

If anyone is interested, you can get the full code from this gist. Just click "Download Zip" and put the code of the zip in an "application" package of a JavaFX project and start the Main class. The difference to the code in the question is in the Particle.java class. In the question the particles are used as nodes and moved in the animation timer, but in this answer only the data is used for drawing an image on a canvas in the animation timer, the node itself isn't put on the scene.

You can use the Settings class in order to specify resolution, number of new particles per frame, etc.

This screenshot shows 3 repellers and 25400 particles on the screen in Full-HD resolution, running at 60fps:

enter image description here

Roland
  • 18,114
  • 12
  • 62
  • 93
  • Just my 2cents: The best way to verify your perf changes is just by drawing 1px by 1px rectangles with multiple color changed randomly across the canvas vs imageview for a small duration. You will observe for some reason canvas outperforms imageview when you have large points to color or draw. In other case same thing can be achieved by Java2D using awt by drawing onto an image, but consumes lot of memory. So my conclusion, if you too many points to draw go with JavaFX canvas. As I've tried 1million points to draw every 1s, on a decent machine and works good. – zIronManBox Mar 15 '16 at 15:06
1

I think it might also help to combine these two lines:

// apply force: gravity
allParticles.stream().parallel().forEach(Particle::applyForceGravity);

// move particle
allParticles.stream().parallel().forEach(Particle::move);

kind of like this:

allParticles.stream().parallel().forEach(particle -> {
  particle.applyForceGravity();
  particle.move();
});

This way you don't get twice the overhead.

EzPizza
  • 979
  • 1
  • 13
  • 22
  • 2
    This may lead to inaccurate simulation though, as it means the forces are calculated according to non-realistic intermediate state. Simply put - you have to first calculate all the forces *according to the current state* and only then transition (move) into the new state. – Itai Dec 13 '17 at 09:28
  • 1
    That depends on whether the particles act out forces on each other, too.If they don't, then there is nothing to worry about. – EzPizza Dec 14 '17 at 09:53