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.

Reading Response – Week 2

Casey Reas’ Eyeo 2012 talk, Chance Operations, made me think differently about randomness in art. He shows how even data from something as steadfast as a natural component can generate patterns that are surprising and meaningful when processed through a system with rules. It made me realize that randomness isn’t just chaos, rather it can actually be a tool if some limits are set on this. That idea challenged how I usually approach making things; I used to tend to want complete control, but maybe leaving room for the unexpected could make my work more interesting and dynamic.

For my own work for this weeks production, I’ve tried experimenting with randomness in things like layout, placement, or small visual details so the final piece isn’t predictable. For me, the balance between control and chance is having a framework that guides the work but still allows it to surprise me. I don’t want total randomness, because then it wouldn’t feel like my work at all, but I also don’t want it completely fixed, because then it might feel rigid or boring. I believe we can not overstate the attempt of finding the sweet spot between dictation and improvisation that allows the bounds of structure, and at the same time, manages to surprise with the output.

Week 2 – A Simple Work of Art

Concept:

I wanted to make something playful and unpredictable using what we have learned so far: loops, conditionals, and simple shapes. The idea was to fill the canvas with a grid, but instead of every cell looking the same, each square of the grid gets randomly assigned a circle, a square, or a diagonal line. This way, the overall structure feels ordered (in code, not compilation), but the details are different every time you run (or click) the sketch.

Code snippet to highlight:

The piece of code I’m most proud of is the tiny decision that controls the orientation of the lines. Despite it being only an If condition, it has quite an effect on how the final image looks. Without it, all the lines would lean the same way, and the grid would look stiff. With it, each line can either go \ or /, which suddenly makes the whole composition feel more dynamic and unpredictable.

stroke(random(50, 200), random(50, 200), random(50, 200), 220);
strokeWeight(random(1, 4));
let orientation = int(random(2)) // decides orientation of the line
if (orientation == 0) {
  line(i, j, i + space, j + space);
} else {
  line(i + space, j, i, j + space);

 

Reflection and Prospects:

What I like most about this sketch is how structured chaos plays out. The grid keeps everything orderly, but within each cell, there’s randomness that gives the piece character, resulting in the chaotic image we get./
If I were to push it further, I’d try:
– Rotating the rectangles randomly, not just keeping them straight.
– Introducing a shifting color palette that changes slowly over time.
– Adding a gentle animation so the grid breathes instead of sitting still.
For now, though, I think it’s a fun little experiment in how tiny tweaks (like flipping a line) can completely change the energy of a generative artwork.

Reading Reflection – Week#2

When Casey Reas presented on the section of the interplay between order and chaos in art, I was struck by the way geometry became a central thread in the works he showed. What first appears as random begins with a point, then extends into a line, a plane, a pyramid, and beyond. This progression made me reflect on how art can serve as a medium for visualizing different dimensions, not just one or two, but conceptually even higher dimensions that are difficult to visualize mathematically.

These pieces also reminded me of a saying that what we perceive as “fixed reality” is often the outcome of countless random events in the past. This questioned me to reconsider the very idea of randomness. In the digital world, randomness is never truly random but rather pseudo-random, generated by algorithms. If randomness is always mediated by machines, codes, and computational logics, then perhaps chance itself is never pure but always carefully curated within larger systems, or even by higher-dimensional “creatures.”

In the last works that Reas showed, randomized pixels can be flipped into recognizable icons and numbers. This reminded me that the rules and symbolic systems we rely on every day, language, notation, even code itself, are not inevitable truths but constructed layers that emerged from countless iterations, decisions, and constraints. What seems stable is in fact the result of “layered randomness,” structured into order through history and standardization. Even today, the simple random() function in p5.js, which feels effortless to call, is built upon decades of infrastructural layering: from punch cards and military research to modern standards like Unicode. Each of these conveniences conceals a history of constraints, distilling complex philosophies and technologies into modular tools that allow digital artists to create with apparent spontaneity.

To answer the question of the optimum balance between total randomness and complete control, my mind drifted toward a more philosophical side. Can there ever be an autonomous “optimal point,” or is every balance ultimately surveilled and regulated? I find myself leaning toward a more pessimistic answer, suspecting that what appears to be freedom within randomness is always already framed by invisible structures of control. 

Week 2 Loop Assignment – Joy Zheng

Concept

My concept for this work came from an in-class moment. When Professor Mang was demonstrating looping horizontal and vertical lines, my eyes were tricked into seeing illusory diagonal lines that weren’t actually there.

After chatting with a psychology major friend who is taking a course on visual perception, I learned this effect is called optical illusion (our brain “fills in” missing shapes or creates illusions). She also introduced me to a set of famous illusions, and I was especially fascinated by the Cafe Wall Illusion, where parallel lines look slanted (not parallel as they really are) when black and white tiles are offset. I decided to recreate that illusion using nested loops, because it is simple yet powerful in showing how math and art can merge to create perceptual and artistic effects.

The effect turned out to be amazing. You can actually feel the tiles appear sloped when you look at them from a certain distance. I noticed that the illusion doesn’t work really well when you are too close.

Embedded sketch

The interactivity of p5.js also inspired me to experiment with different ways of breaking the illusion, so I found that adding bold lines between the tiles is a surprisingly effective method.

A highlight of some code that you’re particularly proud of

My favorite pieces of code are the row offset logic and interactive show lines feature:

for (let y=0; y<rows; y++) {
  //all even rows' first tile was pushed half way in   
    let offset = 0;
    if (y%2 == 1) {
      offset = tileSize/2;
    }
    //for every column
    for (let x = 0; x < columns; x++) {
      //all even row tiles are white
      //all odd row tiles are black
      if (x%2 == 0) {
        fill(0);
      } else {
        fill(255);
      }
      //create tileSize tiles
      //created half way in when it's on the even row
      rect(x * tileSize+offset, y * tileSize, tileSize, tileSize)
    }
    
  }
  //display the breaking illusion lines when clicked
  if (showLines) {
    fill(150);
    for (let y=1; y <rows; y++) {
    rect(0, y*tileSize, width, 5);
    }
  }

This small conditional offset, embedded in a loop, shifts every other row half a tile, which is what makes the illusion possible. The most basic function of showing lines gives me the immediate initiative to stop the illusion. I specifically love these parts because it shows how a very minimal algorithm can have a huge impact on the final visual. Without it, the grid reverses back to ordinary.

Reflection and ideas for future work or improvements

This project inspired me to think about how simple loops and conditions can recreate complex psychological effects. It reminded me that coding is not only technical but also artistic—it can play with human perception in surprising ways. In the future, I would like to expand this piece by making the illusion interactive: for example, letting the user adjust the tile size or the offset with a slider, or animating the tiles so that the illusion shifts dynamically. I would also like to try building other illusions, like the Zollner illusion or the Kanizsa triangle, to see how far p5.js can push visual trickery.