Skip to main content Link Menu Expand (external link) Document Search Copy Copied



Getting Started

You need to have Java 16 or higher. (Contrary to the app, you don’t need Windows for this.)

Add this to your build.gradle:

repositories {
    maven { url "" }

dependencies {
    implementation "com.github.tom-mohr:particle-life:v0.2.0"

You can now import the framework in Java:

import com.particle_life.*;


Instantiate the Physics class by passing an Accelerator to its constructor.


Accelerator particleLife = (a, pos) -> {
    double rmin = 0.3;
    double dist = pos.length();
    double force = dist < rmin ?
            (dist / rmin - 1) : a * (1 - Math.abs(1 + rmin - 2 * dist) / (1 - rmin));
    return pos.mul(force / dist);

Physics physics = new Physics(particleLife);

Running the Simulation

That is, compute the new velocity and position of each particle. This is done by a call to Physics.update().

while (true) {
    // run the simulaton for one step
physics.shutdown(1000);  // just call this at the end of your program

Asynchronous Updating

If you want to do something else while the simulation is running. (Such as rendering the particles and providing a UI.)

Loop loop = new Loop();
loop.start(dt -> {
    // dt = time passed in loop (in seconds)
    physics.settings.dt = dt;  // set dt dynamically
while (true) {  // some GUI loop
    if (button.pressed()) {
        // physics.update() could be running right now
        loop.enqueue(() -> {
            // this command should not run while
            // physics.update() is running, therefore
            // wrap it in a call to loop.enqueue()
            physics.settings.wrap = false;

This example shows how to start an asynchronous loop that repeatedly calls the Physics.update() method.

When using this approach, all changes to the state of Physics (such as changes to Physics.settings or Physics.particles) should be wrapped with a call to loop.enqueue(). You can pause the execution with loop.pause = true.

Number of Update Threads

Use Physics.preferredNumberOfThreads to control the number of threads that are used in the Physics.update() method.

Note that this is independent of whether you use synchronous or asynchronous updating.

Particle Coordinates

Particles have a position, a velocity and a type. The coordinates of the position are in the range of [-1.0, 1.0]. Therefore you may need to map them to screen coordinates when drawing the particles.

By default, Physics.settings.wrap is true. This allows the particles move and interact across the world’s borders. If Physics.settings.wrap is false, they will collide with the world’s borders.


Accelerators decide how particles behave if they come close to each other.

More precicely, they decide how much an individual particle should accelerate and in what direction.

For that, they are only provided with very limited information:

  • the factor between the type of this particle and the type of the neighbor particle
  • the position of the neighbor particle, relative to this particle

Accelerators must implement the Accelerator interface. You can do this with a lambda expression:

Accelerator myAccelerator = (a, pos) -> {
    return new Vector3d(0, 0, 0);  // no acceleration

Pass this to the constructor of the Physics class:

Physics physics = new Physics(myAccelerator);

To change the accelerator later, simply set the accelerator attribute of your Physics instance:

physics.accelerator = myAccelerator;

For example, you could implement the rules of Particle Life like this:

Accelerator particleLife = (a, pos) -> {
    double rmin = 0.3;
    double dist = pos.length();
    double force = dist < rmin ?
        (dist / rmin - 1) : a * (1 - Math.abs(1 + rmin - 2 * dist) / (1 - rmin));
    return pos.mul(force / dist);

But that is just one possible accelerator, and there are many other accelerators that show interesting behaviour.





The values that are passed to the Accelerator when updating the particles’ velocities.

That is: If a particle of type i meets a particle of type j, then particle i is accelerated with value matrix.get(i, j) and particle j is accelerated with value matrix.get(j, i).

Naturally, this matrix is quadratic. Its size is equivalent to the number of types. That is, if you change the size of the matrix, you need to make sure that all particles in Physics.particles that have a type <= Physics.settings.matrix.size(). (Types start with 0, just like the indices in the matrix.)