Simulating Volumetric Ferrofluid Spikes in 2D with Python and py5
2026/9/10
Creative coding often involves taking a complex physical phenomenon and distilling it down to its most visually striking essence. In this sketch, Kinetic Ferrofluid Magnetic Spikes, I set out to simulate the iconic, spiky geometry of ferrofluid reacting to magnetic fields—without resorting to heavy 3D rendering engines or complex fluid dynamics solvers. By leveraging py5 (the Python version of Processing) and NumPy, we can create a convincing volumetric, 3D-like object entirely out of layered 2D geometry and Perlin noise. The result is a glossy, obsidian mass of spikes that morphs dynamically over time, set against a deep, glowing crimson and orange background. Python, paired with py5, is an excellent tool for this kind of experimentation. It allows us to rapidly prototype mathematical concepts and see their visual output in real-time, making it perfect for generating rich, cinematic textures using simple mathematical principles.
Visual & Aesthetic Approach
The core challenge was achieving a volumetric, 3D appearance using only a 2D canvas. To do this, I employed a parallax layering technique. Instead of calculating a 3D mesh and applying lighting, the script draws 20 concentric, overlapping 2D polygons. As the layers progress from the outer edge to the center, they shrink in scale and shift in color—from a glowing red/orange outline to a dense, dark core.The "spikes" characteristic of ferrofluid are generated using multi-octave 2D noise. A low-frequency noise layer provides the fluid's gentle, undulating base shape, while a heavily thresholded, high-frequency noise layer creates sharp, aggressive spikes. Additive blending (py5.blend_mode(py5.ADD)) across these layers accumulates color and alpha values, giving the final structure a dense, luminous quality that mimics subsurface scattering.Code & Technical Breakdown
The illusion relies entirely on two core components: the vertex calculation (which generates the spikes) and the parallax drawing loop (which generates the volume).1. Shaping the Spikes with Thresholded Noise
The get_vertex() function calculates the distance from the center for any given angle. To simulate ferrofluid spikes, we blend two different OpenSimplex noise fields.def get_vertex(angle, layer_scale, t):
x_n = np.cos(angle)
y_n = np.sin(angle)
# 2D noise for the base undulating shape
n1 = py5.os_noise(x_n * 2.0, y_n * 2.0, t * 0.5)
# High frequency noise for the spikes
n2 = py5.os_noise(x_n * 6.0 + 100, y_n * 6.0 + 100, t)
# Threshold the noise so spikes only appear at peaks
spike = max(0, n2 - 0.6) * 4.0
# Combine base radius, soft noise, and sharp spikes
r = BASE_RADIUS * layer_scale + n1 * 300 * layer_scale + spike * 400 * layer_scale
return x_n * r, y_n * rBy mapping the 1D polar angle to a 2D circular coordinate (x_n, y_n) before sampling the noise, we ensure the noise wraps seamlessly without a visible seam at 0/360 degrees. The magic happens in the spike calculation: by subtracting 0.6 and clamping with max(0, ...), the noise remains flat most of the time, erupting into sharp peaks only when the noise value is exceptionally high.
2. Creating Fake 3D Volume through Layering
Instead of a single polygon, the draw() loop stacks 20 iterations of the shape, shrinking it inwards.# Draw several overlapping spiked circles for fake 3D depth
num_layers = 20
py5.blend_mode(py5.ADD)
py5.no_stroke()
# Iterate from back to front (largest to smallest)
for i in range(num_layers, 0, -1):
layer_scale = py5.remap(i, 0, num_layers, 1.0, 0.1)
# Color mapping: Outer layers are red/orange, inner layers are dark
c_r = int(py5.remap(i, 0, num_layers, 30, 255))
c_g = int(py5.remap(i, 0, num_layers, 5, 100))
c_b = int(py5.remap(i, 0, num_layers, 5, 50))
alpha = int(py5.remap(i, 0, num_layers, 20, 200))
py5.fill(c_r, c_g, c_b, alpha)
py5.begin_shape()
for j in range(NUM_POINTS):
angle = py5.TWO_PI * j / NUM_POINTS
# Adding slight rotation offset per layer for parallax
angle_offset = angle + i * 0.05 * np.sin(t)
vx, vy = get_vertex(angle_offset, layer_scale, t + i * 0.05)
py5.vertex(vx, vy)
py5.end_shape(py5.CLOSE)As the loop steps down, layer_scale shrinks the geometry. The color dynamically remaps from vibrant orange/red on the outer shells to a dense, glowing core. Furthermore, angle_offset shifts the rotation of each layer slightly based on a sine wave. This twist creates a parallax effect, making the spikes appear to curve and spiral in 3D space as they grow outward from the center.
