Thanh Quach
Project
Create a natural system simulation, considering environment, agents, and development over time. Use classes, vectors, and randomness for representation and control.
In my country, we call this flower Portulaca grandiflora, also known as the "10 o'clock flower" because it typically blooms most vibrantly at 10 a.m.
For my project, I want to explore how to capture this behavior by creating an interaction based on the day-night cycle. In the morning, the flower will bloom, and at night, it will close or wither. This cycle will guide the experience and reflect the natural rhythm of the flower.

In setting up this code, I created an interactive environment where a flower opens and closes its petals based on the mouse position, and bugs and stars respond to the time of day. I used Perlin noise to add realistic, random movement to the bugs and swaying oval, which makes everything feel more lifelike. One of the biggest challenges was getting the day-to-night transitions to look smooth. I spent a lot of time tweaking the lerpColor and transition values to make the gradient shift naturally between colors as the scene changes from day to night.
At the end, I have to ask my assistant, which the CHAT GPT to help me with this.
let numPetals = 15; // Initial number of petals
let radius = 50;
let angleOffset = 0;
let oval; // Oval object
let bugs = []; // Array to hold bug objects
let stars = []; // Array to hold star objects
let transition = 0; // Transition value for smooth effect
let wind; // Wind force
function setup() {
const p5Container = document.querySelector("#p5-container");
let w = p5Container.clientWidth;
let h = p5Container.clientHeight;
let cnv = createCanvas(w, h);
cnv.parent(p5Container);
cnv.style("position", "absolute");
cnv.style("inset", 0);
cnv.style("z-index", -1);
noStroke();
// Initialize the oval object
oval = new Oval(width / 2, height / 2);
// Initialize a slower wind force
wind = new Force(0.1, 0); // Reduced horizontal wind force
// Create stars and initialize their positions and alpha values
for (let i = 0; i < 100; i++) {
stars.push(new Star(random(width), random(height), random(100, 255)));
}
// Create bugs with random initial positions
for (let i = 0; i < 10; i++) {
bugs.push(new Bug(random(width), random(height)));
}
}
function draw() {
// Background colors
let bgColor1 = color(0, 102, 204); // Blue color for day
let bgColor2 = color(0, 0, 139); // Dark blue color for night
let yellow = color(255, 255, 0); // Yellow color for blend
let blendedColor = lerpColor(bgColor1, yellow, transition); // Blend between blue and yellow
// Set gradient background blending light blue, yellow, and dark blue for night
setGradient(0, 0, width, height, lerpColor(blendedColor, bgColor2, transition), bgColor2);
stroke(0, 128, 0);
strokeWeight(7);
line(width / 2, height / 2, width / 2, height);
// Central Circle
let centerX = width / 2;
let centerY = height / 2;
fill(255, 204, 0);
noStroke();
ellipse(centerX, centerY, 100, 100);
// Calculate number of petals based on mouse position
let mouseYNormalized = map(mouseY, 0, height, 0, 1); // Normalized mouseY value between 0 and 1
numPetals = 15 - floor(mouseYNormalized * 15); // Reduce petals based on mouseY
// Draw the main flower
let flower = new Flower(centerX, centerY, numPetals, radius);
flower.display();
// Draw stars if it's night
if (transition >= 1) {
for (let star of stars) {
star.display(); // Display each star
}
}
// Update the oval's forces
oval.applyForce(wind); // Apply wind force
oval.update(); // Update position
oval.display(); // Display the oval
// Update and display each bug
for (let bug of bugs) {
bug.update();
bug.display();
}
angleOffset += 0.03; // Update angle for flower petals
// Update transition value based on mouse position
if (mouseY < height / 2) {
transition = constrain(transition - 0.01, 0, 1); // Day transition
} else {
transition = constrain(transition + 0.01, 0, 1); // Night transition
}
}
// Flower class
class Flower {
constructor(x, y, numPetals, flowerSize) {
this.x = x;
this.y = y;
this.numPetals = numPetals;
this.flowerSize = flowerSize;
}
display() {
for (let i = 0; i < this.numPetals; i++) {
let angle = TWO_PI / this.numPetals * i + angleOffset;
let petalX = this.x + cos(angle) * this.flowerSize;
let petalY = this.y + sin(angle) * this.flowerSize;
fill(255, 255, 0); // Bright yellow
ellipse(petalX, petalY, 30, 30); // small circle (petals)
}
}
}
// Star class
class Star {
constructor(x, y, alpha) {
this.position = createVector(x, y);
this.alpha = alpha;
}
display() {
fill(255, this.alpha);
ellipse(this.position.x, this.position.y, 3, 3); // Draw each star
// Randomly adjust the star's alpha for a twinkling effect
if (random() < 0.02) { // 2% chance to change each frame
this.alpha = random(100, 255); // Set a new random alpha value
}
}
}
// Bug class
class Bug {
constructor(x, y) {
this.position = createVector(x, y);
this.noiseOffsetX = random(100); // Random offset for horizontal noise
this.noiseOffsetY = random(100); // Random offset for vertical noise
this.size = random(5, 10); // Random size for the bug
}
update() {
// Use Perlin noise for smooth movement
let noiseValueX = map(noise(this.noiseOffsetX), 0, 1, -2, 2); // Horizontal movement
let noiseValueY = map(noise(this.noiseOffsetY), 0, 1, -2, 2); // Vertical movement
this.position.x += noiseValueX; // Update x position
this.position.y += noiseValueY; // Update y position
// Increment noise offsets
this.noiseOffsetX += 0.01; // Adjust speed of horizontal movement
this.noiseOffsetY += 0.01; // Adjust speed of vertical movement
// Wrap around the edges of the canvas
this.position.x = constrain(this.position.x, 0, width);
this.position.y = constrain(this.position.y, 0, height);
}
display() {
fill(0, 255, 0); // Green color for the bug
noStroke();
ellipse(this.position.x, this.position.y, this.size, this.size); // Draw the bug
}
}
// Oval class
class Oval {
constructor(x, y) {
this.position = createVector(x, y);
this.angle = 0; // Initial angle for swaying
this.amplitude = 5; // Amplitude of swaying
this.frequency = 0.05; // Frequency of swaying
this.noiseOffset = random(100); // Random offset for noise
}
applyForce(force) {
this.position.add(force); // Apply force to the position
}
update() {
// Use Perlin noise for smooth swaying
let noiseValue = noise(this.noiseOffset) * this.amplitude;
this.position.y += noiseValue; // Adjust the y position using noise
// Increment noiseOffset to create animation
this.noiseOffset += 0.01;
// Keep the oval within the canvas boundaries
this.position.x = constrain(this.position.x, 0, width);
this.position.y = constrain(this.position.y, 0, height);
}
display() {
fill(139, 69, 19); // Brown color
noStroke(); // No stroke for the oval
ellipse(this.position.x, this.position.y, 15, 10);
}
}
// Force class
class Force {
constructor(x, y) {
this.vector = createVector(x, y); // Vector representing the force
}
get() {
return this.vector; // Return the force vector
}
}
// Function for drawing gradient background
function setGradient(x, y, w, h, c1, c2) {
for (let i = y; i <= y + h; i++) {
let inter = map(i, y, y + h, 0, 1);
let c = lerpColor(c1, c2, inter);
stroke(c);
line(x, i, x + w, i);
}
}
the day-night cycle.