In this mini project, I created three sketches with generative motion with math functions. You can click on the projects' link above. More detailed information will be introduced below as well.
A thick line extends horizontally across the screen, with its color dynamically changing through a spectrum. The line normally follows a baseline at the center, but when the mouse is pressed, it reacts by moving toward the cursor’s vertical position, simulating city buildings and skyscrapers. Once the line reaches the right edge, the canvas resets, the background is cleared, and the color palette subtly shifts.
Sin-Wave creates a row of animated rectangles that move and change color in a wave-like motion, arranged like the sine curve. Each rectangle wave vertically follows a sine function, with different phase shifts and flows. At the same time, their colors smoothly transition between two defined colors, giving a gradient effect that evolves over time. The combination of synchronized motion and shifting hues produces a dynamic vibe when the wind blows across your face at sunset.
A color-shifting ball moves with smooth noise-driven acceleration inside the canvas, bouncing off edges while reversing direction. The ball's collision with a "wall" leads to a new color change: the red channel depends on the ball’s vertical position, while the green channel depends on the horizontal position. The blue one changes with over frame count with a sine wave. The background is decided by the ball's previous color, and is only updated when collision. The noise function makes the movement of the ball more natural, like a ball trapped and lost in a room of ever-changing colors.
More specific and complete code can be found in the project link. Here are some explanations for detailed implementations.
function draw() {
// Color: compute stroke color from a phase angle (piColor).
// cos() drives RED, sin() drives BLUE, GREEN stays maxed (255).
// map() converts outputs into valid RGB.
stroke(map(cos(piColor), -1, 1, 0, 255),
255,
map(sin(piColor), -1, 1, 0, 255));
strokeWeight(10);
// First point: preX, preY; Destination Point: curX, curY
line(preX, preY, curX, curY);
baseLineEnd += 1; // linear movement
preX = curX;
preY = curY;
curX = baseLineEnd;
// set vertical position for next point
if (mouseIsPressed){
curY = mouseY;
}
// if going out of the right edge
// clear canvas and subtle color change
if (curX > width){
curX = 0;
preX = 0;
baseLineEnd = 0;
background(0);
piColor += PI/12;
}
}
function draw() {
noStroke();
rectMode(CENTER);
background(220);
// color ratio, which changes as time (framecount) goes
ratio = (sin(frameCount * 0.02) + 1) / 2;
// Each rectangle:
// - has a different horizontal position (rectW/2 * n)
// - moves up and down with a sine wave (creates the wave motion)
// - has a fill color based on lerpColor(c1, c2, some ratio)
// where the ratio is slightly offset for each rect, so the colors vary
fill(lerpColor(c1, c2, ratio));
rect(0 + rectW/2 * 1, height/2 - sin(rectH * 0 + frameCount*0.05) * marginY, rectW, rectH);
fill(lerpColor(c1, c2, (ratio * 2) % 1));
rect(0 + rectW/2 * 3, height/2 - sin(rectH * 1 + frameCount*0.05) * marginY, rectW, rectH);
fill(lerpColor(c1, c2, (ratio * 3) % 1));
rect(0 + rectW/2 * 5, height/2 - sin(rectH * 2 + frameCount*0.05) * marginY, rectW, rectH);
fill(lerpColor(c1, c2, (ratio * 4) % 1));
rect(0 + rectW/2 * 7, height/2 - sin(rectH * 3 + frameCount*0.05) * marginY, rectW, rectH);
...
...
...
fill(lerpColor(c1, c2, (ratio * 9) % 1));
rect(0 + rectW/2 * 17, height/2 - sin(rectH * 8 + frameCount*0.05) * marginY, rectW, rectH);
fill(lerpColor(c1, c2, (ratio * 10) % 1));
rect(0 + rectW/2 * 19, height/2 - sin(rectH * 9 + frameCount*0.05) * marginY, rectW, rectH);
// 10 times
}
function draw() {
background(lastR, lastG, lastB);
// Random-like acceleration from Perlin noise, mapped to small values
let ax = map(noise(frameCount * 0.01), 0, 1, -0.1, 0.1);
let ay = map(noise(frameCount * 0.01 + 999), 0, 1, -0.1, 0.1);
// Update velocity and keep it within [-3, 3]
speedX += ax;
speedY += ay;
speedX = constrain(speedX, -3, 3);
speedY = constrain(speedY, -3, 3);
x += speedX;
y += speedY;
// ball bouncing with color change
// R and G channels are decided by bouncing position
if (x > width - r) {
x = width - r;
speedX *= -1;
lastR = curR;
curR = map(y, 0, height, 0, 255);
}
if (x < r){
x = r;
speedX *= -1;
lastR = curR;
curR = map(y, 0, height, 0, 255);
}
if (y > height - r) {
y = height - r;
speedY *= -1;
lastG = curG;
curG = map(x, 0, width, 0, 255);
}
if (y < r) {
y = r;
speedY *= -1;
lastG = curG;
curG = map(x, 0, width, 0, 255);
}
lastB = curB;
curB = map(sin((frameCount%200) * 0.1), -1, 1, 0, 255);
fill(curR, curG, curB);
ellipse(x, y, r * 2, r * 2);
}
Using the math functions and conducting various motions is really interesting! What I found most challenging was not designing or thinking of the way some elements moved, but figuring out how to implement as desired. I had to try more than once and made several adjustments when utilizing frameCount and sin/cos to see the visual outcome. Some other design inspiration also popped up when I was diverging from "the right path".