Water ripple effect in p5.js

2 minute read

Link to the source code here

Water ripples

This is a super simple implementation of the “water ripple” effect, inspired by Daniel Shiffman’s Coding challenge on the same topic.

Core of the algorithm is:

  • two matrices of numbers, one for the current instant and one for the previous.
  • A dampening factor (could be a value between 0.0 and 1.0, but the best visual results happens between 0.75 and 0.99999).
  • A “propagation” process that picks the value from the previous instant and mixes them with the current instant.

The propagation process can be something like:

current[x][y] = (
        previous[x-1][y] + 
        previous[x+1][y] +
        previous[x][y-1] +
        previous[x][y+1]
    ) / 2.0 - current[x][y];

current[x][y] *= dampening;

This will pick the values in the cell above, below, on the left and on the right of the current cell.

+-------+-------+-------+
|       |       |       |
|       | x,y-1 |       |
|       |       |       |
+-------+-------+-------+
|       |       |       |
| x-1,y |  x,y  | x+1,y |
|       |       |       |
+-------+-------+-------+
|       |       |       |
|       | x,y+1 |       |
|       |       |       |
+-------+-------+-------+

Variations

While experimenting with this algorithm, I’ve tried doing some changes in the way the values are picked, by picking the diagonal values as well, but decreasing their value by a factor of 2:

current[x][y] = (
        previous[x-1][y] + 
        previous[x+1][y] +
        previous[x][y-1] +
        previous[x][y+1] +
        (
            previous[x-1][y-1] +
            previous[x-1][y+1] +
            previous[x+1][y-1] +
            previous[x+1][y+1]
        ) * 0.5
    ) / 3.0 - current[x][y];

current[x][y] *= dampening;

And here the result:

Link to the source code here.

This result in a lightly round-ish shape of the wave generated, at the expense of 4 more values to be evaluated.

+---------+---------+---------+
|         |         |         |
| x-1,y-1 |  x,y-1  | x+1,y-1 |
|         |         |         |
+---------+---------+---------+
|         |         |         |
|  x-1,y  |   x,y   |  x+1,y  |
|         |         |         |
+---------+---------+---------+
|         |         |         |
| x-1,y+1 |  x,y+1  | x+1,y+1 |
|         |         |         |
+---------+---------+---------+

With a modern machine this algorithm still runs at about 60fps1.

  1. P5.js has a framerate limit of 60 fps by default. This can be confirmed by running this script where every 10 frames the current framerate is updated, and every 1000 frames the max framerate is reset (this allow a window of 1000 frames circa to see what the max framerate actually is). There are a bunch of calls to array functions, but the performance impact is quite low and there should still be an average framerate way above 60fps (here the link to the performance benchmark of the javascript array.reduce function). The text draw function and all the other calculations are quite fast as we can have sketches with per pixel calculations in a 400x200 frame, meaning an average of 3 operations per pixel to evaluate RGB colors and two calls to get and set the pixels of the frame.