Mini Project 5 is a further development for my midterm project. It is based on Mini Project 4 and combined a motion I created in Mini Project 3.
What I have done in Mini Project 5 includes 2 parts:
First, I combine the background habitat with the dynamic creature and make according adjustments as well.
Refine Concentric-Circle-Set Central Points rolling to make sure, in most cases, they won't overlap each other. I used do-while loops to conduct the process. It should be noted that due to the randomness of CC sizes and numbers, an infinite loop might occur in some extreme situations. Thus, I set a max loop number for the do-while loop of the third set of concentric circles.
function bgMetadataInit(
breathCCRStart,
breathCCREnd,
breathCCNumStart,
breathCCNumEnd
) {
// sin wave variables
amp1 = random(30, 100);
amp2 = random(30, 90);
amp3 = random(30, 120);
// breath Concentric Circle variables
breathCCR1 = random(breathCCRStart, breathCCREnd);
breathCCR2 = random(breathCCRStart, breathCCREnd);
breathCCR3 = random(breathCCRStart, breathCCREnd);
breathCCNum1 = random(breathCCNumStart, breathCCNumEnd);
breathCCNum2 = random(breathCCNumStart, breathCCNumEnd);
breathCCNum3 = random(breathCCNumStart, breathCCNumEnd);
// make sure at least 4 CCs can appear in the canvas
breathCC_XCenter1 = random(4 * breathCCR1, width - 4 * breathCCR1);
breathCC_YCenter1 = random(4 * breathCCR1, height - 4 * breathCCR1);
do {
breathCC_XCenter2 = random(4 * breathCCR2, width - 4 * breathCCR2);
breathCC_YCenter2 = random(4 * breathCCR2, height - 4 * breathCCR2);
} while (
dist(
breathCC_XCenter1,
breathCC_YCenter1,
breathCC_XCenter2,
breathCC_YCenter2
) <=
breathCCR1 * breathCCNum1 + breathCCR2 * breathCCNum2
);
// avoid infinite loop
let maxTries = 100;
let tries = 0;
do {
breathCC_XCenter3 = random(4 * breathCCR3, width - 4 * breathCCR3);
breathCC_YCenter3 = random(4 * breathCCR3, height - 4 * breathCCR3);
tries++;
} while (
(dist(
breathCC_XCenter3,
breathCC_YCenter3,
breathCC_XCenter1,
breathCC_YCenter1
) <=
breathCCR3 * breathCCNum3 + breathCCR1 * breathCCNum1 ||
dist(
breathCC_XCenter3,
breathCC_YCenter3,
breathCC_XCenter2,
breathCC_YCenter2
) <=
breathCCR3 * breathCCNum3 + breathCCR2 * breathCCNum2) &&
tries < maxTries
);
if (tries >= maxTries) {
print(
"Warning: failed to place 3rd CC set without overlap after",
maxTries,
"tries"
);
}
breathCCangleTotalNum = 3;
}
Second, I reconstruct the overall structure of my code with functions and translate() & rotate()
For example, the initial setup() follows this structure:
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");
}
New version:
function bgMetadataInit(
breathCCRStart,
breathCCREnd,
breathCCNumStart,
breathCCNumEnd
) {
// sin wave variables
amp1 = random(30, 100);
amp2 = random(30, 90);
amp3 = random(30, 120);
// breath Concentric Circle variables
breathCCR1 = random(breathCCRStart, breathCCREnd);
breathCCR2 = random(breathCCRStart, breathCCREnd);
breathCCR3 = random(breathCCRStart, breathCCREnd);
breathCCNum1 = random(breathCCNumStart, breathCCNumEnd);
breathCCNum2 = random(breathCCNumStart, breathCCNumEnd);
breathCCNum3 = random(breathCCNumStart, breathCCNumEnd);
// make sure at least 4 CCs can appear in the canvas
breathCC_XCenter1 = random(4 * breathCCR1, width - 4 * breathCCR1);
breathCC_YCenter1 = random(4 * breathCCR1, height - 4 * breathCCR1);
do {
breathCC_XCenter2 = random(4 * breathCCR2, width - 4 * breathCCR2);
breathCC_YCenter2 = random(4 * breathCCR2, height - 4 * breathCCR2);
} while (
dist(
breathCC_XCenter1,
breathCC_YCenter1,
breathCC_XCenter2,
breathCC_YCenter2
) <=
breathCCR1 * breathCCNum1 + breathCCR2 * breathCCNum2
);
// avoid infinite loop
let maxTries = 100;
let tries = 0;
do {
breathCC_XCenter3 = random(4 * breathCCR3, width - 4 * breathCCR3);
breathCC_YCenter3 = random(4 * breathCCR3, height - 4 * breathCCR3);
tries++;
} while (
(dist(
breathCC_XCenter3,
breathCC_YCenter3,
breathCC_XCenter1,
breathCC_YCenter1
) <=
breathCCR3 * breathCCNum3 + breathCCR1 * breathCCNum1 ||
dist(
breathCC_XCenter3,
breathCC_YCenter3,
breathCC_XCenter2,
breathCC_YCenter2
) <=
breathCCR3 * breathCCNum3 + breathCCR2 * breathCCNum2) &&
tries < maxTries
);
if (tries >= maxTries) {
print(
"Warning: failed to place 3rd CC set without overlap after",
maxTries,
"tries"
);
}
breathCCangleTotalNum = 3;
}
// one CCSet
function createBreathCCSet(
breathCC_XCenter,
breathCC_YCenter,
breathCCNum,
breathCCR
) {
push();
translate(breathCC_XCenter, breathCC_YCenter);
for (let i = 0; i < breathCCNum; 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 = breathCCR * 2 * (i + 1);
arc(0, 0, d, d, a1, a0 + CCangle);
}
pop();
}
// Initialization functions
function createBgColorAndWave(c1, c2, c3, c4) {
let angle1, y1, angle2, y2, angle3, y3;
strokeWeight(1);
//Background
// width for each segment
const segW = width / (totalColorNum - 1);
for (let x = 0; x < width * 2; x++) {
// width 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, (0, 1)
let cRatio = (x - seg * segW) / segW;
// make racial smooth
let tEase = (1 - cos(PI * cRatio)) / 2;
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);
}
stroke(curColor);
line(x, 0, x, height);
// Since Waves
// fill color is white with transparency
fill(0, 30);
// I don't use noStroke() to make sure the sin wave
// can blend with the background
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 + sin(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);
}
}
function create3CCSets() {
// Concentric Circles
noFill();
stroke(255, 50);
strokeWeight(1);
createBreathCCSet(
breathCC_XCenter1,
breathCC_YCenter1,
breathCCNum1,
breathCCR1
);
createBreathCCSet(
breathCC_XCenter2,
breathCC_YCenter2,
breathCCNum2,
breathCCR2
);
createBreathCCSet(
breathCC_XCenter3,
breathCC_YCenter3,
breathCCNum3,
breathCCR3
);
}
,in which all codes pile up inside the setup() function. I separate different sections and created respective functions:
Currently, I use translate(), push(), and pop() only for drawing the concentric circle sets, and in fact, my current usage is still very simple, with translating to the circle point only, since a for loop can draw circles in the same CC set in a better way. The most struggling thing might be the fact that the former version has addressed this challenge in a way that does not really accommodate further reconstruction with translate(), push(), and pop(). However, these functions will be used more when I go on with the design of the main creature in a few days.
This part is already included in the second part of my code explanation. Take it as a reference for more detail. What I would like to mention is that for:
function bgMetadataInit(
breathCCRStart,
breathCCREnd,
breathCCNumStart,
breathCCNumEnd
)
,
function createBreathCCSet(
breathCC_XCenter,
breathCC_YCenter,
breathCCNum,
breathCCR
)
, and
function createBgColorAndWave(c1, c2, c3, c4)
, I made parameters to be passed into the functions. All of them are about the "basic information" of the background generation, such as Concentric-Circle-Set size (RStart & REnd) and total circle numbers (NumStart & NumEnd), and their corresponding positions. It is for random initialization in each canvas, clear (the if statement when the main line is running to the right end in the draw() function). For the color initialization, although currently I am using a static color set for the background, I made them parameters still in case I would like to make some changes in background colors in the draw() function.
Using translate(), push(), and pop() made my creature more organized and flexible. With translate(), I could easily move different parts of my design to specific positions without changing their actual drawing code. The push() and pop() functions helped me keep transformations separate, so each module is drawn independently without affecting others. This made the code cleaner and easier to modify. In addition, using user-defined functions allowed me to structure my code better since I can reuse part of my code without copying and pasting.