Sea Creatures with Elementary Functions
By Juan Carlos Ponce Campuzano, 16/Feb/2026
Introduction
I recently came across the work of the coder and artist known as @yuruyurau, and I was immediately drawn to the elegance and compactness of their visual sketches made with p5.js. In particular, a piece shared in this post beautifully demonstrates how fluid, organic motion can emerge from what appears to be a short line of code with only 261 characters.
t=0,draw=$=>{t||createCanvas(w=400,w);background(9).stroke(w,46);for(t+=PI/30,i=3e4;i--;)d=mag(k=i<2e4?sin(i/9)*9:4*cos(i/49)*cos(i/3690),e=i/984-12)**2/99+1,point((q=k*(4+sin(d*16-t+k))-5*sin(atan2(k,e)*9))+60*sin(c=d*1.1-t/18+i%2*3)+200,(q+40)*sin(c-d)+d*79)}#つぶやきProcessing pic.twitter.com/fjJpaBUQw1
— ア (@yuruyurau) December 29, 2025
As a mathematician, I became curious about how these animations actually work. How can such intricate, almost underwater-like forms arise from simple elementary functions? That curiosity led me to recreate and explore similar ideas using p5.js, focusing on sine, cosine, and other basic mathematical relationships.
This post is a reflection of my (almost obsessive) exploration. It is an attempt to unpack and understand the mathematical structure behind these mesmerizing animations. My goal is to show how layered simplicity, built from elementary functions, can generate surprisingly rich and organic visual behaviour.
Step 1: Basic Grid Structure
We begin with the simplest possible structure: a static grid of points. This establishes our foundation for transforming linear indices into 2D coordinates.
let t = 0;
function setup() {
createCanvas(500, 500);
background(0);
stroke(255);
}
function draw() {
background(0);
// Create a grid of points
for (let i = 0; i < 20000; i++) {
// Convert linear index to 2D coordinates
const x = i % 200;
const y = floor(i / 200);
// Center the grid
const k = x - 100;
const e = y - 50;
// Place points with some spacing
const px = width / 2 + k * 2;
const py = height / 2 + e * 2;
point(px, py);
}
}
Key concepts
-
Linear index to 2D mapping:
Converting a single loop index into grid coordinates using
x = i % Nandy = floor(i / N). -
Centering the coordinate system:
Shifting the grid so the origin sits at the canvas centre
(
k = x - a,e = y - b). - Structured point field: Establishing a static lattice as the foundation for later transformations.
Step 2: Introducing Polar Coordinates and a Gentle Animation
In this step, we introduce polar coordinates using the mag()
function for distance calculation
and atan2() for angle calculation and also the time
variable t to animate.
let t = 0;
function setup() {
createCanvas(500, 500);
pixelDensity(2);
background(0);
stroke(255, 120);
}
function draw() {
background(0, 40);
t += 0.01; // Update time for animation
for (let i = 0; i < 20000; i++) {
const x = i % 200;
const y = floor(i / 200);
const k = x - 100;
const e = y - 50;
// Calculate distance from center (magnitude)
const o = mag(k, e) / 50;
// Convert to angle
const c = atan2(e, k);
// Simple circular pattern
const px = width / 2 + (k + o * 10 * cos(c + t)) * 2;
const py = height / 2 + (e + o * 10 * sin(c + t)) * 2;
point(px, py);
}
}
Key Concepts
-
Polar coordinates:
Radius
r = sqrt(k² + e²)and angleθ = atan2(e, k). - Distance-based modulation: Using radial distance to control displacement magnitude.
-
Time parameter:
Introducing a variable
tthat increments every frame. -
Angular displacement:
Circular motion generated with
cos(θ + t)andsin(θ + t).
Step 3: Adding Time-Based Animation and Gentle Distortion
Now we create our first animated distortion effect using sinusoidal functions.
let t = 0;
function setup() {
createCanvas(500, 500);
pixelDensity(2);
background(0);
stroke(255, 80);
}
function draw() {
background(0, 40);
t += 0.02;
for (let i = 0; i < 20000; i++) {
const x = i % 200;
const y = floor(i / 200);
const k = x - 100;
const e = y - 50;
const o = mag(k, e) / 50;
const c = atan2(e, k);
// Add time-based oscillation
const q = o * 20 * sin(c * 2 + t);
const px = width / 2 + (k + q * cos(c)) * 2;
const py = height / 2 + (e + q * sin(c)) * 2;
point(px, py);
}
}
Key Concepts
-
Sinusoidal modulation:
Creating wave-like motion with
sin(2θ + t). - Radial amplification: Scaling oscillations by distance from the centre.
-
Directional displacement:
Applying deformation along radial directions using
cos(θ)andsin(θ). - Emergent wave patterns: Interference between angle and time produces organic motion.
Step 4: Complex Warping Functions
We increase complexity by adding multiple trigonometric functions and changing the coordinate scaling.
let t = 0;
function setup() {
createCanvas(500, 500);
pixelDensity(2);
background(0);
stroke(255, 80);
}
function draw() {
background(0, 40);
t += 0.03;
for (let i = 0; i < 30000; i++) {
const x = i % 250;
const y = floor(i / 250);
// Different centering and scaling
const k = x / 2 - 62.5;
const e = y / 2 - 50;
const o = mag(k, e) / 20;
const c = o * e / 20 - t / 5;
// More complex warping function
const q = x + o * k * sin(2 * o - t);
const px = width / 2 + q * sin(c);
const py = height / 2 + (y / 2) * cos(2 * c - t) - q * cos(c);
point(px, py);
}
}
Key Concepts
- Rescaling and reframing: Adjusting coordinate scaling to alter density and proportions.
- Composite angle definitions: Building angles from multiple variables (distance, position, time).
- Layered trigonometric transformations: Combining sine and cosine functions in nested expressions.
-
Nonlinear warping:
Distorting the grid using multiplicative interactions such as
o * k * sin(...).
Step 5: Final Organic Animation
The complete sketch with all transformations combined, creating the organic sea-creature motion.
let t = 0;
function setup() {
createCanvas(500, 500);
pixelDensity(2);
background(0);
stroke(255, 80);
}
function draw() {
background(0, 60);
t += 0.05;
for (let i = 0; i < 40000; i++) {
// High density grid with different modulo division
const x = i / 4 % 100;
const y = floor(i / 150);
// Recenter with scaling
const k = x / 4 - 12.5;
const e = y / 9 - 9;
// Polar coordinate calculations
const o = mag(k, e) / 9;
const c = o * e / 30 - t / 8;
// Complex displacement function
const q =
x +
cos(9 / (k + 1e-6)) + // Avoid division by zero
o * k *
sin(4 * o - t);
// Final position with multiple transformations
const px = 0.9 * q * sin(c) + width / 2;
const py =
height / 2 +
(y / 3.6) * cos(3 * c - t / 2) -
(q / 2) * cos(c);
point(px, py);
}
}
Key Concepts
- High-density sampling: Increasing point count for smoother visual texture.
- Singularity handling: Preventing division by zero with a small epsilon value.
- Multi-layered deformation: Combining reciprocal, trigonometric, and radial terms.
- Interference of frequencies: Multiple time-dependent components interacting.
- Emergent complexity: Organic motion arising from stacked elementary functions.
Final Mathematical Breakdown
The complete transformation used in the last step can be expressed as: \begin{align*} x &= \frac{i}{4} \mod 100 \\ y &= \lfloor \frac{i}{150} \rfloor \\ k &= \frac{x}{4} - 12.5 \\ e &= \frac{y}{9} - 9 \\ o &= \frac{\sqrt{k^2 + e^2}}{9} \\ c &= \frac{o \times e}{30} - \frac{t}{8} \\ q &= x + \cos\left(\frac{9}{k + \epsilon}\right) + o \times k \times \sin(4o - t) \\ x' &= 0.9 \times q \times \sin(c) + \frac{\text{width}}{2} \\ y' &= \frac{\text{height}}{2} + \frac{y}{3.6} \times \cos(3c - t/2) - \frac{q}{2} \times \cos(c) \end{align*} where $\epsilon = 10^{-6}$ prevents division by zero.
Creating Variations Through Experimentation
Now that we have deconstructed the animation to its
mathematical core, the creative process becomes a game
of exploration. By adjusting the various constants,
scaling factors, and frequencies within the formulas,
you can generate an endless variety of patterns and motions.
Try modifying values like the divisor in
mag(k, e) / 9, the coefficients in front of t,
or the numbers inside trigonometric
functions such as sin(4 * o - t). You can even
introduce new terms or replace existing ones with
different functions like tan() or noise()
from the p5.js library. This process of trial
and error is where unexpected and fantastic mathematical
patterns emerge, transforming the code from a
static algorithm into a dynamic tool for artistic discovery.
Final remarks
I hope the steps described above provide a clearer understanding of how complex organic motion emerges from the careful layering of simple mathematical transformations. By starting with basic concepts and progressively adding complexity, we can create sophisticated animations that mimic natural phenomena. The key insight is that intricate patterns arise from the interplay of trigonometric functions, polar coordinates, and time-based variations.
Below are several variations of the same underlying structure. All of them are adaptations inspired by @yuruyurau's elegant visual sketches. Each version emerges from small adjustments in constants, frequencies, and scaling factors. What fascinates me most is how delicate the balance is — tiny numerical changes can completely reshape the creature. This is where mathematics stops feeling mechanical and starts feeling alive.
Sea creature 1 Sea creature 2 Sea creature 3 Sea creature 4
🪼 See the full Gallery HERE
Finally, if you find this content useful, please consider supporting my work using the links below.
I sincerely appreciate it. Thank you! 😃