Mini Project 4 creates the habitat of Luxuria, a dangerous bacterium. More information about Luxuria can be found here. Luxuria was born in the universe and transmitted by the lightness in the deep dark. Thus, Breath of the Universe aims at creating the living condition of Luxuria at its genesis, and might be changed by Luxuria's motion in future development.
The whole scene consists mainly of three parts:
Gradient Background
Sin / Cos waves
Breath Concentrate Circles
I started with the background. I would like to explore a gradient background with gradient colors. I first used lerpColor for the whole canvas, but later thought of my needs for a variety of colors in the future. Then I adjusted the segment's ability to contain more colors with a for loop. Currently, I use 4 colors in three segments to show the current gradation. The 2nd sketch is generated with changes in the order of colors used.
Ordered color from light blue to dark blue:
(c1, c2) -> (c2, c3) -> (c3, c4)
The order of the blue colors' darkness is changed:
(c1, c2) -> (c2, c3) -> (c3, c4)
Add a since wave to create a sense of "gravitational waves" in the regular gradient background
(c1, c2) -> (c2, c3) -> (c3, c4)
Then I would like to add something that matches the concept of the universe. It occurred to me that "gravitational waves" can be depicted with an obscure style. So I first explored the beginShape () and endShape() functions to draw a Sine wave (Sketch 3). However, I found integrating the wave and the background challenging since a Shape will use the same color while I would like to have the wave gradient. Thus, I turned to draw a series of circles, reusing the stroke designed for the pre-completed background part. To my surprise, lines and circles overlap each other and make the final result somehow 3-D, like a waving ribbon (Sketch 4). I am contended with the unexpected outcome, and generated two more waves with a slight difference in height, amplitude, and wave type (Sketch 5).
Use circles to depict a Sin wave with strokes in the corresponding background color.
3 waves in total, with the amplitude and height randomly / differently generated. The current second wave is a Cos wave.
"Breath of the Universe", which is inspired by black holes and astrolabes.
Finally, I wanted to create a static image that conveys a sense of dynamism and randomness—like the “breath of the universe.” I got inspiration from black holes and astrolabes, and first designed a set of concentric circles (Sketch 6). Then, I transformed the circles into arcs, introducing randomness in the positions of the arc openings. Each time the canvas refreshes, the arrangement changes, creating an impression of rotation and motion (Sketch 7). In the end, I expanded the design into three sets of “breathing” concentric circles, where the number of circles, their sizes, and their positions are also randomized (Sketch8 - 9).
The set of concentric circles transforms from a complete, continuous form into broken, segmented arcs, giving the impression of rotation and disruption.
Adding the sense of breathing and motion for the whole scene
The final version generates 3 breath concentric circle sets and 3 curve waves with randomness in position and shape.
Here are some explanations for detailed implementations with in-line comments. Different parts are marked in different colors.
Gradient Background
Sin / Cos waves
Breath Concentrate Circles
const totalColorNum = 4;
let backSinAmp = 100;
let backgroundBallR = 30;
let sinWaveNum = 3;
let amp1, amp2, amp3;
let breathCCR1, breathCCR2, breathCCR3;
let breathCCNum1, breathCCNum2, breathCCNum3;
let breathCCangleTotalNum;
let breathCC_XCenter1, breathCC_XCenter2, breathCC_XCenter3;
let breathCC_YCenter1, breathCC_YCenter2, breathCC_YCenter3;
function setup() {
createCanvas(800, 500);
// background variables
// smaller ligher
let c1 = color(10, 133, 255); // Blue1
let c2 = color(0, 102, 204); // Blue2
let c3 = color(0, 30, 152); // Blue3
let c4 = color(0, 0, 112); // Blue4
// sin wave variables
amp1 = random(30, 100);
amp2 = random(30, 90);
amp3 = random(30, 120);
// breath Concentric Circle variables
breathCCR1 = random(10, 25);
breathCCR2 = random(10, 25);
breathCCR3 = random(10, 25);
breathCCNum1 = random(4, 10);
breathCCNum2 = random(4, 10);
breathCCNum3 = random(4, 10);
breathCC_XCenter1 = random(4 * breathCCR1, width - 4 * breathCCR1);
breathCC_YCenter1 = random(4 * breathCCR1, height - 4 * breathCCR1);
breathCC_XCenter2 = random(4 * breathCCR2, width - 4 * breathCCR2);
breathCC_YCenter2 = random(4 * breathCCR2, height - 4 * breathCCR2);
breathCC_XCenter3 = random(4 * breathCCR3, width - 4 * breathCCR3);
breathCC_YCenter3 = random(4 * breathCCR3, height - 4 * breathCCR3);
breathCCangleTotalNum = 3;
//Environment Implementation
//Background
// width for each segment
const segW = width / (totalColorNum - 1);
for (let x = 0; x < width * 2; x++) {
// width * 2 is to make sure the sin wave can be totally drawn
// Current segement (0/1/2)
let seg = floor(constrain(x / segW, 0, totalColorNum - 2));
// cRatio within (0, 1)
let cRatio = (x - seg * segW) / segW;
// make racial smooth
let tEase = (1 - cos(PI * cRatio)) / 2;
// assign color based on the segment index
let curColor;
if (seg == 0) {
curColor = lerpColor(c4, c2, tEase);
} else if (seg == 1) {
curColor = lerpColor(c2, c3, tEase);
} else if (seg == 2) {
curColor = lerpColor(c3, c4, tEase);
}
// use lines & stroke to generate the gradient background
stroke(curColor);
line(x, 0, x, height);
// Since Waves
// fill color is white with transparency
// I don't use noStroke() to make sure the sin wave
// can blend with the background
// 3 waves in total with the amplitude and height randomly / differently generated
angle1 = map(x, 0, width, PI / 3, TWO_PI * 2 + (0 * PI) / 3);
y1 = height / 2 + sin(angle1) * amp1 - 200;
circle(x, y1, backgroundBallR * 2);
angle2 = map(x, 0, width, (2 * PI) / 3, TWO_PI * 2 + (1 * PI) / 3);
y2 = height / 2 + cos(angle2) * amp2;
circle(x, y2, backgroundBallR * 2);
angle3 = map(x, 0, width, (3 * PI) / 3, TWO_PI * 2 + (2 * PI) / 3);
y3 = height / 2 + sin(angle3) * amp3 + 200;
circle(x, y3, backgroundBallR * 2);
}
// Concentric Circles
noFill();
stroke(255);
strokeWeight(1);
// set 1
for (let i = 0; i < breathCCNum1; i++) {
const CCangle = TWO_PI;
const base = (PI / 6) * i;
let a0 = base + random(0, PI / 6); // start of the blocked segment
let a1 = base + PI / 6 + random(0, PI / 6); // end of the blocked segment
a0 = ((a0 % CCangle) + CCangle) % CCangle;
a1 = ((a1 % CCangle) + CCangle) % CCangle;
if (a1 <= a0) a1 += CCangle; // ensure a1 > a0
// the size
const d = breathCCR1 * 2 * (i + 1);
// draw only the white part: the complement of [a0, a1]
arc(breathCC_XCenter1, breathCC_YCenter1, d, d, a1, a0 + CCangle);
}
// set 2
for (let i = 0; i < breathCCNum2; i++) {
const CCangle = TWO_PI;
const base = (PI / 6) * i;
let a0 = base + random(0, PI / 6);
let a1 = base + PI / 6 + random(0, PI / 6);
a0 = ((a0 % CCangle) + CCangle) % CCangle;
a1 = ((a1 % CCangle) + CCangle) % CCangle;
if (a1 <= a0) a1 += CCangle;
const d = breathCCR2 * 2 * (i + 1);
arc(breathCC_XCenter2, breathCC_YCenter2, d, d, a1, a0 + CCangle);
}
// set 3
for (let i = 0; i < breathCCNum3; i++) {
const CCangle = TWO_PI;
const base = (PI / 6) * i;
let a0 = base + random(0, PI / 6);
let a1 = base + PI / 6 + random(0, PI / 6);
a0 = ((a0 % CCangle) + CCangle) % CCangle;
a1 = ((a1 % CCangle) + CCangle) % CCangle;
if (a1 <= a0) a1 += CCangle;
const d = breathCCR3 * 2 * (i + 1);
arc(breathCC_XCenter3, breathCC_YCenter3, d, d, a1, a0 + CCangle);
}
// saveCanvas("yourName", "png");
}
I find programming the computer to draw significantly different from drawing by hand from observation. First, drawing by hand needs an overall structure before drawing, but programming actually gives me more inspiration with unexpected coding results, which might lead to outcomes different from the initial overview. Drawing by hand also requires the ability to control tools for accuracy, with little tolerance for error, while programming drawing can be frequently changed with slight differences in code to generate prodigiously distinct canvases without too much effort. However, they still share the similarity that we need to observe and make adjustments from time to time to refine our final result.
The properties I manipulated in the iterations include:
Shape (arc position and opening)
Size/radius
Color gradients or opacity
Position (Starting point/height)
Number of repetitions
What I observed is that repetitions can also be influenced by randomness to generate a more natural scene. The overlap of different repetitions might lead to surprising and inspiring results as well. Thus, a good generative pattern should reach a balance between order and variation with clear rules defining the construction of different parts.