Category: F2025 – Mang
Week 3 – Reading Response
The author differentiates “tools” for utility, such as the hypothetical Nintendo fridge, from something “fun” and “interactive”. Thus, he raises a question, “Is interactivity utterly subjective?”, only to discuss the process of interactivity as a subjective flow. In particular, he argues that the thinking that spurs creativity–and a certain level of randomness as we discussed last week–is a crucial element of interactivity.
I agree that the thinking for creative responses is the most important part. In the past, even now, some low- and mid-level interactive creations, as the author would categorize, are solely dependent on a set of rules that only attempt to be generative. Their output doesn’t serve a meaning itself, only reflecting part of a bigger scene defined by the rule setter. Ideally, however, every output should prompt some further thinking in the receiver of the response, or the originator of the conversation. It is indeed fairly difficult to achieve so, particularly in the past.
The advent of Generative AI could bring some change, especially when it’s seemingly untouched in the interactive visual art sphere. What if, I say what if, some code is written in real-time, following a certain broader set of rules? What if, in addition to a set of rules, some new impromptu visual rules are created in real time?
Week 2 – Blackboard
For my project, I decided to add an element of interactivity. I was inspired by the visuals that were posted. I initially intended to make an “experience”, where I could use randomized texts to create unique shapes, however, I decided to create a simple blackboard instead. The idea had initialized in the library rooms, where my friends and I were studying for Calculus. My peer started writing on the whiteboard, and he had to keep on erasing previous work to progress further, when I wondered “what if we had a digital white board? One that erases on command?”
//if pixel is active, check how long it's been active
if (pixels[i][j] > 0) {
//stay white for 2 seconds
if (millis() - pixels[i][j] < 2000) {
fill(255);
} else {
//time expired
pixels[i][j] = 0;
fill(0);
}
} else {
//default state is black
fill(0);
}
I am especially proud of this part of my code. It took me 5 hours just skimming through pages and brain-numbingly consuming youtube videos to figure out how to make my pixels change colour and stay and go back to default.
//on left mouse press, activate the pixel under the cursor
if (mouseIsPressed && mouseButton === LEFT) {
//convert mouse position to grid coords
let i = floor(mouseX / pixelSize);
let j = floor(mouseY / pixelSize);
//only update if inside the canvas
if (i >= 0 && i < cols && j >= 0 && j < rows) {
//store the activation time
pixels[i][j] = millis();
}
}
}
I also enjoyed programming this part of m code, where I had to find the location of the cursor and change the pixel colour.
Overall, I think I incorporated the loops smoothly into my project, however, I wanted to integrate a “menu bar”, with and eraser and a pen colour. I hope to involve more complex interactions in my upcoming assignments.
Week 2 Reflection – Organized Chaos
Reas’ talk on chance operations fascinated me and made me reflect on how “organized chaos” is present in our everyday life, such as jazz. Jazz musicians often make up melodies on the spot, riffing off each other. There’s a theme, but most of the melodies come from being in the moment and catching the rhythm. That’s what I think most of Reas’ projects are where the “non-human agent” creates them; the experience or art comes to be by following a basic line of code to give a unique piece of visuals.
The video opened my eyes to how one can use randomness to be creative. Before watching Reas’ talk, I used to attach a negative connotation to “chaos”. That it means destruction. So rather than welcoming it, I would try to eliminate variables that would disrupt the aesthetics or homogeneity of my projects. I realized that allowing a little bit of “chance” would help me discover new experiences, such as attending a surprise dinner party would allow me to form relationships with incredibly, grounded people.
What stood out to me the most is the perfect balance and tension of chaos and order. Reas’ examples of using very simple shapes, like dots and dashes, or even a single line of code from the 10 PRINT program indicated that there’s beauty in a set of instructions mixed with randomness.
Week 2 – art work
Turning Math into Motion: Playing with Sine and Cosine in p5.js
I’ve always liked the concept of sine and cosine waves. They’re simple functions on paper, just waves that go up and down forever. But I was wondering what would it make if it’s involved in creating virtual art. So I decided to use them to make something that looks alive on the screen.
The Concept
The artwork is based on the idea of ripples. Every click on the canvas sends out a circle that grows, but instead of being a perfect circle, it wobbles slightly — almost like the ripple is remembering where it’s been. Over time, these ripples overlap and fade, and the whole thing feels less like a diagram and more like a memory unfolding.
I think that’s what I love about it: it’s math doing what math does, but the output feels alive.
Highlights in the Code
I don’t want to break it down line by line, but a few details stood out to me while I was building this:
1. The Wavy Distortion
let offset = 15 * sin(this.t / 10);
ellipse(this.x, this.y, radius + offset);
Here, the sin() function is doing all the work. Instead of the circle growing in a boring, linear way, sin(this.t / 10) gives me a smooth back-and-forth motion between -1 and 1. Multiplying by 15 stretches that into a range of -15 to +15.
So when I add offset to the radius, the circle doesn’t stay steady. It breathes, gently expanding and shrinking as it grows. That tiny detail is what makes the ripple feel alive rather than mechanical.
2. The Colors That Shift on Their Own
stroke(
map(sin(this.t / 70), -1, 1, 100, 255),
map(cos(this.t / 25), -1, 1, 100, 255),
map(sin(this.t / 30), -1, 1, 150, 255),
180
);
At first, I hard-coded colors, but it looked flat. Then I realized I could let sine and cosine drive the colors, too.
Here’s how it works:
sin() and cos() output values between -1 and 1.
map() stretches those into ranges that make sense for color (e.g., 100–255).
Because the math is always oscillating, the stroke color is always changing — slowly shifting through different shades.
It’s not random; it’s predictably unpredictable. That’s why the transitions feel smooth instead of jarring.
3. Here is where I used loops:
function draw() {
background(10, 10, 20, 30);
for (let r of ripples) {
r.update();
r.show();
}
}
Each click I add one ripple to an array of ripples where I do the get bigger – wave distortion effect. And here I loop over them to update and show the effect.
Every ripple has its own this.t, which increases every frame. That’s what drives everything: the size, the offset wobble, the color shifts. Without t, nothing would move. With it, the math unfolds frame by frame, and the artwork comes alive.
Week 2 – Looped
This week we’re asked to create an artwork that incorporates the “loop” concept in code. I saw dynamic squares before, and would personally like to create a grid that gently “breathes” and drifts. Each square’s size, brightness are driven by layered sine waves using a shared time phase so the whole field feels organic and connected, like a low‑key pixel ocean.
Below is the code for core motion + styling logic (the vibe engine).
const w = sin(phase + (x * 0.35) + (y * 0.45)); // wave seed const s = map(w, -1, 1, cell * 0.25, cell * 0.85); // size pulse const dx = sin(phase * 0.7 + x * 0.3) * 6; const dy = cos(phase * 0.7 + y * 0.3) * 6; const hueVal = (x * 8 + y * 6 + frameCount * 0.4) % 360; const bri = map(w, -1, 1, 35, 90); fill(hueVal, 60, bri, 0.9); rect(px, py, s, s, 6);
- What works: Simple math, no arrays or heavy state—scales nicely with window size. The motion feels smooth and unified.
- Limitations: All squares animate uniformly; interaction is missing. No colors. (In a version, colors follow a fixed formula, so longer viewing gets predictable.)
To be frank, this implementation still lacks the smooth “sea wave” vibe that I was looking for. In particular, I would have liked the edges to transform into non-linear lines like waves. But I would call this a first prototype as a p5.js beginner.
However, I trialed for smaller square size, and I’m surprised that such a minor change created something perceptually different. Finally, I implemented a super cool mouse click effect, which in a way achieved another level of dynamic aesthetics.
Week 2 – Kaleidoscope
Concept
When I was trying to come up with an idea on what art to create, I tried to sketch different styles, 3D, pressable, not pressable, colorful, black and white. But I wasn’t fully satisfied with the end result and couldn’t get myself to code. I was scrolling shorts and I saw one that said “Why do we still have these in a store?” and the first thing he showed was a kaleidoscope (link to short: https://youtube.com/shorts/vnFYE48zqIA?si=TGT7zf0O8515eiQ_).
When I was a child kaleidoscope was my favorite thing ever, but I would also break it eventually to get the pretty stones out of it (lmao). So then I thought, why not recreate that pattern? I also thought that the most interesting art is usually interactive. That was when I came up with the idea to make a kaleidoscope, an individual one, the one that people can customize and interact with.
Production
I didn’t really understand how to make it work and this YouTube tutorial helped me a lot: https://youtu.be/KcHqRAM0sEU?si=AfDeL56RTEBYEjbl . I watched it and customized the code as I wanted.
I first made a usual one:
Press C to clear the canvas.
But then I wanted to make it more interesting, so I added a function where the sketch disappears every second:
Try drawing simple circles and rectangles in one place along the rhythm of one second
Difficulties and favorite part of code:
The code itself is very short and was’t so difficult to make. What actually challenged me was understanding how to do it initially. And once I got the concept I was able to create without further struggles.
One part of the code that I liked was the function that draws a line in a random color, and then draws a vertically mirrored “reflection” of that line.
function drawReflection(x, y, px, py) {
strokeWeight(7);
stroke(random(255), random(255), random(255));
line(x, y, px, py);
push();
scale(1, -1);
line(x, y, px, py);
pop();
}
Conclusion:
I really enjoyed seeing how a small code can create very interesting customizable designs, and overall it was really interesting to do.
Week2 – Reading Response
The video inspired me with my week 2 project. Similar to Casey’s examples, I wanted to create a piece that is randomly generated, a work where I don’t fully decide the outcome but instead design the conditions for it to emerge. What stood out to me is that randomness on its own often creates noise, but randomness framed by rules creates variation that still feels intentional. That’s why, in my own project, I didn’t let the lines fall anywhere on the canvas. If the verticals were completely random, sometimes they would overlap or sit too close together, which broke the balance I was trying to capture from Mondrian’s style. By enforcing rules, like keeping vertical lines at least 60 pixels apart and limiting where they could end, I was shaping the randomness so it produced results that still looked coherent.
This process made me think more about the balance between total control and total randomness. If I had forced every line into fixed positions, the piece would look the same every time and lose the surprise that makes generative art exciting. But if I gave up all control, the results would drift too far from what inspired me in the first place. The balance is somewhere in the middle: I act like the system builder, defining boundaries and constraints, while letting randomness fill in the details. For me, that tension is where the art lives. Creating a space where the computer surprises me, but always within a framework that reflects my intention.
Week 2 – Simple Art
I always liked Piet Mondrian’s art. Something about the lines and the geometrical shapes was satisfying. Although it is simple, I find his art, particularly his most famous work “Composition with Red, blue and yellow” to be one of my favourite art. I thought his art could be somewhat re-created with code so I decided to give it a shot.
Unlike its simplistic looks, creating this was much harder than I thought. At first, I just selected completely random vertical and horizontal lines. However, I quickly realized that in his work, the lines do not reach from one end to another. It sometimes fell short before reaching the total width or height of the canvas. Choosing random positions of the lines along with random lengths was not an easy process, so I decided to simple things out by controlling the lengths to a certain extent.
I made three rules. The first rule was to choose 2 verticals lines with spaces of at least 60 in between. If it wasnt for this, sometimes the vertical lines overlapped, or the distance between the lines fell so small that the outcome looked a bit ugly. The second rule was to controll the height of the vertical lines. The lines should start from 0, but the end point of the lines had to fall randomly between 60% of the height to the end. The last rule of the lines was to keep the distance between the horizontal lines to be atleast 80 pixels apart.
After setting these rules, I tried generating the lines. However, I was soon met with another problem. The horizontal lines did not intersect perfectly with the vertical lines. So to prevent this, I just placed horizontal lines where the vertical lines ended. I also decided not to play around with the lengths of the horizontal lines to keep things simple.
//the key function that chooses the lines drawn on the canvas
function pickRandomWithSpacing(minVal, maxVal, existing, spacing) {
for (let t = 0; t < 300; t++) {
//first it chooses a random candidate in selecting lines.
//usually selects lines from 0 to either height or width, this is saved as candidate
const candidate = random(minVal, maxVal);
let ok = true;
//now we assume that this is a valid space, and try to check if the distance between the previously selected line and e has enough space.
for (const e of existing) {
//gets the previously selected value,
if (abs(candidate - e) < spacing) { //checks if the distance is valid
//if not, break the loop
ok = false;
break;
}
}
if (ok){ //if the distance is valid, we found the valid line.
return candidate;
}
}
return null; //if we couldnt find it, give up finding to avoid beig stuck in loop
}
The key part for this artwork was randomly choosing the lines in a sort of a controlled manner. If it was chosen completely randomly, it would not look like the orignal art so I had to make some working boundaries. This is the function called pickRandomWithSpacing. It takes 4 variables: minVal, maxVal, existing, spacing. Line needs two points. The starting point of vertical line is chosen from a ranges 0 to width and is saved in array called verticalX with space of 60. The end point of vertical line is chosen from ranges 60%of height to height and is saved in array called verticalEndYs with spacing of 80. The idea of trial and error is applied when finding the correct spacing. When we first choose the space, we assume that it follows the rules that I made. However, it checks if the spaces are correctly picked. If it does not follow the rules, it breaks the loop and chooses another random space which hopes to follow the rule. It ensures that it finds the correct working spacing by repeating the loop 300 times.
This is an example of how its called
//pick two valid vertical lines
for (let i = 0; i < 2; i++) {
//we use the function that we created. from 0 to width, saved in arraynamed verticalX with spacing 60.
let x = pickRandomWithSpacing(0, width, verticalXs, 60);
verticalXs.push(x);
//yEnd is the end value for the vertical line, I wanted to give it a end to the length of it. The vertical lines start at 0 but is randomly chosen when to end. The minimum is 60% of the height, all the way untill height. These values are saved in array calkled verticalEndYs
let yEnd = pickRandomWithSpacing(MIN_VERTICAL_LENGTH, height, verticalEndYs, MIN_Y_SPACING);
verticalEndYs.push(yEnd);
//this pushing is key in finding the cells to color. It saves the coordinates of the lines created in order to select the different sized cells
verticalLines.push({ x1: x, y1: 0, x2: x, y2: yEnd });
}
After the lines were correctly chosen, the next part was correctly identifying the cells created. Since I saved all the nessasary points of the array. I went through several trial and error sessions to identify the cells. I found that there were 9 cells when I applied the rules and focused on finding the cells generated with the random lines. This was much more complicated then I thought because the cell sizes of x and y are completely different and unique. I had a notebook with me that kept track of the coordinates. The key point about identidying cells was finding out with points to skip. When the vertical lines fall short, when identifying the cell that does not include that particular vertical line, it needs to skip it. I was stuck on this for a long time and received advice from openAI on the logistics of it.
The rest of it was easy. I had to pick random colors from the color pallete that I chose. (The color pallete from the original work).
As you see here, I repeated null three times to increases the chances of white rectangles. This is because the original artwork contains more background than the colored ones.
Lastly, I drew the lines on top of the colored rectangles to create the final look.
For future improvements, I can apply random horizontal lengths so that not all horizontal lines reach from one end to another. Also I can also apply controlled randomization in choosing the colors instead of my complete randomization that I have now with colors. In the original artwork, when the color is chosen, the same color should not be next to each other. However, for simplicity I allowed this.
Week 2: Corner Contact (Production)
(You can wait until it hits the corner naturally or hold Mouse1 to Speed it Up)
Before what I have here, this project started with me really curious to experiment with 3D objects, but the WEBGL system got terribly confusing with all sorts of new functions and ways I had to approach drawing shapes. I gave up on that and decided to first focus on 2D for this assignment.
I had a real tough time coming up with an idea after that. I thought about it over the weekend and I landed on the idea that I wanted whatever I made to feel satisfying, so I started thinking of things that are visually satisfying. This reminded me of a YouTube short I recently saw about how the DVD screensaver can ever only touch two opposite corners and NEVER the corner adjacent to them. This fact really lived in my head rent-free for a bit but it also made me wonder if I could create simple physics in p5js to recreate a DVD screensaver with the little JavaScript that I understood.
I started with just a ball and a simple if statement that detected whether the x position of the ball was greater than the width minus the radius, essentially allowing me to cause things to happen when the borders of the ball touched the edge. Once I had both the X and Y movements done, I needed to check for corner contacts.
function edgeBounce(i) {
if (xPos[i] > width - radius[i] || xPos[i] < radius[i]) {
xSpeed[i] *= -1;
}
if (yPos[i] > height - radius[i] || yPos[i] < radius[i]) {
ySpeed[i] *= -1;
}
}
Also, I know these are two dimensional circles but I will continue to refer to them as balls.
The time it would take for the ball to hit one of the corners took a really long time so I wanted to add the ability to speed up the ball animation! At first I tried to use the while() to detect when mouseIsPressed but after a lot of confusion I realized it had to be and if() function instead. However, I learned two important things today: the first was that while-else() was not a thing like if-else() is, and how to return variables from a function. It was such a natural way of learning through trial and error and it felt quite satisfying.
I eventually figured out how to do it by assigning a function, speedUp(), to return a value straight into the multiplier variable.
//somewhere in draw()
for (let i = 0; i < xPos.length; i++) {
// move ball
multiplier = speedUp(i);
//..lots of code inbetween
function speedUp(i){
if (mouseIsPressed){
return timeMultiplier;
} else{
return 1;
}
}
While I was here, I felt like the background looked a little bland at first so I decided to have a faint text in the background that would say the current speed multiplier. I made sure to have it drawn first so it’s always layered underneath the circles.
//MULTIPLIER TEXT textSize(400); textAlign(CENTER,CENTER); noStroke(); fill(multiplierColor); text(speedUp() + "x", width/2, height/2);
And of course the main highlight of this assignment was to use loops. I was actually quite proud of how quickly I got the hang of the for loops and using arrays to take advantage of each loop.
for (let i = 0; i < xPos.length; i++) {
// move ball
multiplier = speedUp(i);
xPos[i] += xSpeed[i] * multiplier;
yPos[i] += ySpeed[i] * multiplier;
edgeBounce(i); //checks for edge touch
cornerBounce(i) //checks for corner touch, creates a new ball if successful
// draw ball
let c = ballColors[i];
fill(c[0], c[1], c[2]); //RGB values of c
stroke(c[0]-20, c[1]-20, c[2]-20); //-20 shifts it a little darker
strokeWeight(8);
ellipse(xPos[i], yPos[i], radius[i] * 2);
}//end of for loop
Difficulties Endured
A lot of tutorials I followed would introduce things I was not familiar with at all so I did workarounds that were probably a lot less efficient.
But most importantly, I crashed so many times and had to redo so much of my work so many times.
I originally wanted to have the new balls come out from the corner that the “motherball” contacted but when I tested my code it ended up spawning an infinite number of balls and caused my tab to completely crash and required me to redo almost 20 minutes of work.
I remember I had to write and rewrite the cornerBounce() function twice and it was really frustrating especially because of how long the if() statement was.
function cornerBounce(i){
if ((xPos[i] <= radius[i] || xPos[i] >= width - radius[i]) &&
(yPos[i] <= radius[i] || yPos[i] >= height - radius[i])) {
console.log("Corner Contact Confirmed")
if(ballCount < maxBallCount){
console.log("Ball #" + xPos.length + " was Created")
xPos.push(width/2); //PUSH adds to an array
yPos.push(height/2); //if spawn in the corner it creates infinite balls
xSpeed.push(random(-8, 8));
ySpeed.push(random(-8, 8));
//assigns a random color
let newColor = random(colorOptions);
ballColors.push(newColor);
radius.push(50);
ballCount += 1;
} else if (ballCount == maxBallCount){
console.log("Max Ball Count Reached")
}//ends the ballcount if()
}//ends the if()
}
Conclusion
Even thought my web editor crashed half a dozen times and disheartened me throughout the process, the trial and error felt like a satisfying loop of learning that will certainly help me navigate JavaScript a little more easily with each passing week.