Background
Metaballs are organic isosurfaces that combine with nearby objects in organically smooth ways. They are often used in computer graphics and can be used to model surface tension in water droplets.
The bounds of a metaball are modeled by the inverse-square law. All the equations of all the component balls are added together to form a falloff function. An arbitrary threshold value is chosen (typically 1); points that are less than the threshold are considered “outside” the metaball, while points that are greater than the threshold are considered “inside” the metaball. Generating an image can be computationally expensive, as the falloff function would need to be calculated at every pixel per frame.
A common approximation is to use marching squares (as outlined in this video by Reducible). This implementation involves subdividing the frame into a grid of squares, then calculating the value of the falloff function at every corner of every square. These values can form a rough estimation of the metaballs’ bounds. While effective, this has the same issue as the naive approach: it is computationally expensive. Increased precision requires exponentially more computational power.
Implementation
The implementation used in the above simulation follows the same core principles: a falloff function and a threshold. The key difference is that the above simulation avoids directly calculating the falloff function, and instead uses existing CSS properties to approximate the function. A gradient can be considered as the equation of a circle, and its border consists of the points where the equation equals some threshold. Because gradients are additive, applying gaussian blur to every component ball and adding together the gradients is equivalent to determining the falloff function. This was accomplished by the following CSS code: filter: blur(15px)
.
The second component is threshold. Unlike the naive and marching squares approaches, there is no direct equation to solve, so there is no way to set a fixed threshold. The solution is to (once again) use an existing CSS property: contrast. Contrasting and thresholding function similarly: larger values get amplified, smaller values become reduced. This was accomplished by the following code: filter: contrast(10000)
.
This completes the metaball implementation. I made a few more additions to my code, mainly animating the ball movement and adding a play/pause button in case the simulation becomes too computationally taxing.