Week 2 – Loop Art

My Concept

For this assignment, I wanted my design to feel both structured and fun to interact with, while still being something I could make using the basics we’ve learned in p5.js. My final sketch creates a grid of animated squares that slowly move and respond to the mouse. When the mouse moves over the canvas, squares close to it change size and color, making the grid feel interactive and fun to play with and explore.

Snippet of the code I’m proud of:

Here’s a section I’m proud of because it combines motion and interaction:

let d = dist(mouseX, mouseY, x, y);

if (d < 80) {
  size += map(d, 0, 80, 25, 0);
  r = 255;
  g = random(100, 200);
  b = random(200, 255);
}

This part checks how close the mouse is to a square (d) and then:

  • increases the square’s size as the mouse gets closer,
  • changes its color using both randomness and mouse position, which creates a dynamic, responsive effect. It shows how loops and conditionals work together with interaction.

Embedded sketch:

How I made this: 

I made this sketch by creating a canvas using createCanvas(600, 400) and then using nested loops with x and y to place squares evenly across the grid. I used a spacing variable to control how far apart the squares are. Each square changes size over time with sin(frameCount * 0.05 + x * 0.04 + y * 0.04) to make a smooth wave effect. I also used dist(mouseX, mouseY, x, y) to check how far the mouse is from each square so that squares near the mouse get bigger and change color. I added a small rotation with rotate() and used push() and pop() so each square rotates on its own. I set the colors using r, g, and b, some based on the wave and some random near the mouse, and drew the squares with rectMode(CENTER) and rect(). Using loops, animation, rotation, and mouse interaction made the piece feel alive while keeping a clear pattern.

The complete code:

let spacing = 45;

function setup() {
  createCanvas(600, 400);
}

function draw() {
  background(10, 10, 30, 60);

  for (let x = 0; x < width; x += spacing) {
    for (let y = 0; y < height; y += spacing) {

      // wave motion
      let wave = sin(frameCount * 0.05 + x * 0.04 + y * 0.04) * 12;
      let size = 18 + wave;

      // distance to mouse
      let d = dist(mouseX, mouseY, x, y);

      // angle rotation
      let angle = sin(frameCount * 0.02 + d * 0.05);

      push();
      translate(x + spacing / 2, y + spacing / 2);
      rotate(angle);

      // color
      let r = 120 + wave * 4;
      let g = 80 + d * 0.2;
      let b = 200;

      // interaction 
      if (d < 80) {
        size += map(d, 0, 80, 25, 0);
        r = 255;
        g = random(100, 200);
        b = random(200, 255);
      }

      stroke(255, 180);
      fill(r, g, b, 180);
      rectMode(CENTER);
      rect(0, 0, size, size);

      pop();
    }
  }
}

Reflection and future ideas

Overall, I’m really happy with how this piece turned out. I started with the idea of a geometric grid like the ones in old computer art magazines, and I used what I’ve learned about loops, motion, and interaction to make it come to life. Learning how to use nested loops was especially rewarding because it let me create a pattern that fills the whole canvas with very little code. I’m honestly very proud of how my code came out. I really enjoyed exploring this aspect of p5.  And i like how I made the squares change when the mouse moves over them which makes it feel alive and fun to explore.

If I had more time or knew more about p5.js, I would like to try more effects. For example, I could make the shapes respond to sound, or create more interactive features like clicking to make new shapes appear. I would also like to experiment with colors to make smoother gradients and more interesting palettes. In the future, I want to keep exploring user interaction and generative art using what I have learned so far and what I will learn next.

References

p5.js Reference: https://p5js.org/reference/

Week 2 – Loop Art

My Concept:

I started by looking at the links to computer graphics magazines. provided. A recurring theme I noticed is the repeated use of squares, circles, and other geometric shapes. So I knew that I wanted my design to incorporate a heavy geometric theme. As a beginner P5 coder, I wanted to make sure I could execute this idea with what I had learned so far. I ended up creating a square-oriented piece that offers the user interactions to play with, inspired by old computer art magazines. The piece features a canvas covered with animated squares across the grid that change color depending on the mouse’s position (left or right). On top of the dynamic background is a moving square that changes direction with a mouse click, creating a final artwork that evolves through loops. 

Highlight of Sketch:

// i is the variable used for the outer loop and controls the x positions across the canvas.
// j is the variable used for the inner loop and controls the y positions down the canvas. 
for (let i = 0; i <= width; i += 25) {
  for (let j = 0; j <= height; j += 25) {
    
    // r and g represent red and green values, and this line of code generates random color values adding some variation to the work.
    // mouseX controls the blue values and allows the user to move the mouse to change the color of the grid.
    let r = random(200,255);
    let g = random(100,200);
    let b = mouseX;
    
    //stroke created by using the red, green, and blue values initiated above. 120 represents the transparency of the circles.
    // ellipse uses the variables i and j to generate squares at each point of the grid.
    stroke (r, g, b, 120);
    noFill();
    rect(i, j, 20, 20);
  }
}

A particular part of the sketch I am proud of is the use of loops in my code. It was something new we had learned this week, and my incorporation of the inner and outer loop resulted in an outcome I am quite satisfied with and proud of. I am also proud of the randomized color variables in my code and of using mouseX to add user interaction with the colors.

Embedded Sketch:

How this was Made:

I started the process by setting my mind on what I wanted my piece to look like. After setting my mind on a specific idea, I then had to plan out how to execute it. I started my code by initializing my global variables, x and y, to store the current position of the moving square. Then I set speedX and speedY to control the direction and speed of the square’s movement. Then I set a lower frame rate with the frameRate() function to make the animation look cleaner and smoother. Then I used nested loops. An inner and outer loop that controls the grid background. The outer loop moves horizontally across (rows) the canvas, and the inner loops move vertically (columns) across the canvas. I set the red, green, and blue values and created random variables for them to add variety to the grid. The blue values are controlled by mouseX, so the user can change the grid color depending on whether the mouse is on the left or right side of the screen. Then I added the lines of code to create the main moving square. Then, using logical conditionals (or), I was able to have the square’s direction reverse once it hits any edge of the canvas. Finally, to add interaction to the code, I added the mousePressed() function, which allows the user to click the mouse to reverse the square’s direction. Using what I have learned so far, I created this fun final artwork.

Reflection and Future Ideas:

Overall, I am quite satisfied with my final piece and how I was able to combine what I have learned so far to create a fun, interactive piece with p5. Although the loop part and randomized variables part came with some trial and error, at the end I was able to overcome those obstacles and have an outcome I am proud of. For the future, I hope to learn more about p5 and create even more impressive pieces of work, specifically, more dynamic animations.

Assignment_2: loop art

Concept

I looked throught some of the old computerarts provided and some of the organized and innerric graphs reminded me of the golden curve and the related math arts that I have seen. I remembered that the seeds of the sunflower followed a certain mathematical pattern and decided to use that as the model for my computer art.

I did use AI in this assignment to understand the pattern and mathematical logic the sunflower seeds followed, and to figure out some basic formulas that would allow me to recreate such shapes, but the codes were my own.

Code chunk

for (let pointn = 0; c * sqrt(pointn) <= 150; pointn++) {
    let angle = pointn * 137.5;
    let x = c * sqrt(pointn) * cos(angle);
    let y = c * sqrt(pointn) * sin(angle);
    
    //The code can be played with using many math functions, I tried some and attached them below
    
    //without sqrt
    //let x = (c * (pointn)/10) * cos(angle);
    //let y = (c * (pointn)/10) * sin(angle);

    // logarith(also looks good when made 1*c)
    // let x =5*c * log(pointn) * cos(angle);
    // let y =5*c * log(pointn) * sin(angle);

    //1/log
    // let x =100*c * 1/log(pointn) * cos(angle);
    // let y =100*c * 1/log(pointn) * sin(angle);

    let r = map(x, -300, 300, 80, 255);
    let g = map(y, -300, 300, 80, 255);
    noStroke()
    fill(r, g, (r + g) / 1.65);
    circle(x, y, 3);

The full code is relatively short so I picked the for loop section because I think it shows exactly what loops are helpful for. This chunk of code greatly utalizes the efficency of loops. It would be extremely difficult and time consuming to draw all the dots with accurate spacing by repeated lines of code. The for loop allows drawing and editing of the dots in one line, making it much easier. I also thouht it is intereating that I am able to create different patterns using only change in the mathematical function and slight adjustment to numbers, so I included those modified codes as well.

I was able to recreate the sunflower pattern in resting first, but I thought it looked a bit dull. Because it was a round pattern and included curves, I decided to make it rotate around the center to have a bit of a dillusional effect. I looked up a video tutorial on youtube and learned how to move start point to the center of the canvas and how to rotate it. The effect is pretty good.

Reflection

Drawing a mathematical pattern probably does not really fit the requirement of doing computer art, but I believe the Fibonacci numbers and its related curves do all look quite artistic, and the massive amount of dots needed is an excellent medium for praticing loops. My finished work seems a bit easy and a bit short for an assignment, and I might decide to try something more complicated and in accordance with class difficulties in future assignments.

Another thing is that AI really is quite helpful in the case of understanding scientific or math related concepts. I wouldn’t have been able to figure out all the formulas myself. I will attach my conversation with Gemini in the reference section

References

  • https://www.youtube.com/watch?v=i5bs3SPpHdM
  • https://www.youtube.com/watch?v=z9d1mxgZ0ag
  • https://www.youtube.com/watch?v=3U8-9_WeuKE
  • https://www.youtube.com/watch?v=_GkxCIW46to
  • https://gemini.google.com/share/4c9531505d04

Assignment 2: Box Hit

Here is my final sketch:

Concept:

For this project, I was inspired by the game Smash Hit, where a ball hits glass and causes it to shatter. Also, while looking through the computer art magazines, I came across Boxes I by William Kolomyjec. I decided to combine these two ideas by creating a grid of squares with a circle in the center that acts as a button that triggers the distortion of the squares. 

The circle turns green when the viewer hovers over it and turns red when clicked. Once you click on it, the circle causes the squares surrounded by it to distort, kinda like in the artwork I was inspired by. I wanted a controlled system that slowly breaks apart, while having a game-like effect. 

Here is the artwork I was inspired by that was in the magazine:

Code Highlight:

The code that I am particularly proud of is the section that distorts the boxes based on their distance from the center and the randomness to its position and sides. It gave the visual effect I wanted. 

//distortion control using the radius
if (ruined && radius < 155) {
  //so if the disortion is on and the radius hasn't reached its maximum, expand the radius. I chose the number 155 because I didn't want the disortion to go all the way out.
  radius += 4; //to increase the radius gradually (like a firework effect)
}

if (!ruined) {
  //if the distortion is off to reset the radius back to 0.
  radius = 0;
}

//the grid of boxes
for (
  let x = 0;
  x <= width - size;
  x += size //so it can loop horizontly
) {
  for (
    let y = 0;
    y <= height - size;
    y += size //so it can loop vertically
  ) {
    let d = dist(x, y, width / 2, height / 2); //to get the distance from the squares to the center of the canvas

    //to make the grid distorted
    if (ruined && d < radius) {
      // if the distortion is on and the square is in near the radius
      rect(
        x + random(-5, 1),
        y + random(-5, 1),
        size + random(-5, 1),
        size + random(-5, 1)
      ); //to make random x and y positions and change the width and height for a random messy effect. (for the disorted squares)
    } else {
      // if the squares are not within the radius.
      fill("white");
      rect(x, y, size, size);
    } // let the rest of the boxes to be straight and untouched
  }
}

Reflection/future work:

In class, we used loops to create a grid of circles, so I applied the same concept using nested for loops to build a grid of squares. At first, the size of each box was 50, but I felt that it was too large for the distortion effect to look effective. The larger size also caused some of the boxes to be cut off at the edges of the canvas. So I decided to reduce the size of the boxes to 20, which made the grid feel more refined and detailed. 

Also, the distortion effect was happening too fast it felt overwhelming. I tried to slow it down by reducing the values inside the random function, but this did not work the way I expected. So I just did the frameRate function and put it inside the setup function, like how you showed us in class. I think slowing down the frame rate helped the movement feel more controlled and intentional.

When the circle was clicked, all the boxes that were within the radius distorted at the same time. I was going to leave it this way, but I showed my friend, and she told me that I should try to make the distortion spread outward gradually, like a firework. I did this by using a radius that slowly increases over time, allowing it to expand from the center instead of appearing all at once. I think it made it more visually interesting, so I stuck with it.

While working on this project, I sometimes needed help with the order of the functions and how everything should be structured so the sketch would work properly. I already knew the functions from the slides and reference materials and wrote the code myself, but I was not always sure how to organize them in an order that would work. So I used ChatGPT and Google searches to help clarify those details, specifically how to control the distortion using a radius value and why that needs to be placed at the top of the draw function so it could update consistently, and where the mouse-pressed function should be placed. But most of my understanding came from the class examples, lecture slides, and trial and error with my code. 

In the future, I would like to add more details and incorporate more concepts that were previously discussed. I am interested in exploring different types of distortion and animation that really enhance the experience. 

Reading Reflection – Week 2

One thing I really loved in Casey Reas’ Eyeo talk on chance operations was how much inspiration came from bundling architecture. Looking at real neighborhoods and constructions, and then creating them into systems of rules and geometry, made the work really interesting and intentional. That approach really resonated with me, especially because I see a lot of overlap with Casey’s lecture and what we talked about in my Understanding IM class. We talked about the Dada movement and how chance was used as a response to control and structure after WWI, and many of the artworks, like the Jean Arp piece shown in the video, overlapped with examples we’ve already studied. In both of these cases, chance is used within a structure, which allowed forms to emerge rather than be fully planned. Moreover, to me, this video felt like a short history of digital art, showing how artists moved from pure experimentation to systems where form and behavior emerge over time, reminding me of the book he showed of a million random digits with 100000 normal deviations. I also really liked the idea of making things that are clearly artificial, but still giving them an organic quality.

Before watching this video, I mostly thought of randomness as something that makes things messy and out of control. I think that came from my own experience, when I tried to make a random background for my self-portrait, and the words kept filling the space until it turned completely black. This video helped me realize that randomness is not completely random, but is intentional and planned. There is always a set of instructions and the use of precise geometry behind it. I remember Reas said something along the lines of “pure randomness with symmetry, you start to see faces, and triggering our imagination,” and that stuck with me. In my own work, I will now set clear rules first and then allow small random changes, like with the position or scale. I think that feels like the best balance between control and chaos for me because I still design the system, but do not fully control the outcome. The results can be something better than I planned from the start. I do believe the author is biased in a way by portraying chance positively, like yes, chance can open up to new possibilities, but it only works because of the structure placed around it. The video left me thinking about how much control an artist should give up. Also, I am interested in exploring ideas like a maze or a shape that grows outward from the center, similar to what was shown in the video.

Week 2 Assignment – 3D Conway game of life

Conway game of life 3D sketch below!

Concept:

The inspiration for this is Conway’s game of life, computer generated art is like an umbrella term, I started with searching up old computer generated art in the 50’s and 60’s, and there were some cool concepts like using computer parts to make some sort of art out of them, but it wasn’t exactly what I was looking for. Then it hit me! Conway’s game of life, but not just 2D, that would not work for me, I had to make it 3D, and that’s how this sketch started.

I had to tweak the rules a bit to give this a more generative art feel as well as per-determining the spawn position for the starting cells.

Implementation:

Before we get into the technical implementation of this, let me cover  the theory and rules that the sketch runs by.

Of course for 3D we had to use webgl, to get our 3rd axis (z), and also use orbital control to allow us to move around the area.

createCanvas(400, 400, WEBGL);
// Start with a view of the entire resolution
camera(0, 400, 4900, 0, 0, 0, 0, 1, 0);

orbitControl();

 

A dead cell becomes alive only if it has exactly 3 or 6 neighbours. (Neighbours are alive cells that are next to the cell we are checking)

A living cell stays alive only if it has exactly 5 or 6 neighbours, otherwise it will die.

For the starting spawn, I first started with giving each cell a random chance of 2% to become alive on initialization, it would work however sometimes the  cells would all die or I would not get a good looking design, so I decided to spawn the cells at each corner.

// Create the array for all th cells state
  for (let x = 0; x < res; x++) {
    grid[x] = [];
    next[x] = [];
    for (let y = 0; y < res; y++) {
      grid[x][y] = [];
      next[x][y] = [];
      for (let z = 0; z < res; z++) {
        // Seed with 3x3x3 clusters at corners
        let inCorner1 = x < 3 && y < 3 && z < 3;
        let inCorner2 = x >= res - 3 && y < 3 && z < 3;
        let inCorner3 = x < 3 && y >= res - 3 && z < 3;
        let inCorner4 = x >= res - 3 && y >= res - 3 && z < 3;
        let inCorner5 = x < 3 && y < 3 && z >= res - 3;
        let inCorner6 = x >= res - 3 && y < 3 && z >= res - 3;
        let inCorner7 = x < 3 && y >= res - 3 && z >= res - 3;
        let inCorner8 = x >= res - 3 && y >= res - 3 && z >= res - 3;

        grid[x][y][z] =
          inCorner1 ||
          inCorner2 ||
          inCorner3 ||
          inCorner4 ||
          inCorner5 ||
          inCorner6 ||
          inCorner7 ||
          inCorner8
            ? 1
            : 0;
        next[x][y][z] = 0;
      }
    }
  }

To check for neighbours of each cell, we use a triple nested loop and offset from -1 to 1, to check behind, center forward for each axis.

A couple things to note is, I run the rule checking code once every 30 frames, so that the cells don’t populate too fast and so that we can actually see what is happening and enjoy the chaos that is happening.

A couple things I am proud of is my optimization and coordinate calculation.

Context: There are 2 grids that we are using, our “present” grid and the “next” grid, to not confuse the computer, we apply our rule application and calculation to our present grid, but store the results in our next grid, now originally to switch between the 2 I would turn grid into a long string JSON, then parse it tot equate it to next, but that would lead to thousands of operations. So what I figured out was actually something from c++ and dealing with pointers, and it’s simply just changing the name. To provide context, in javascript you can’t equate the 2 grids to each other to change them, because then they would be connected and if you affect one grid the other is also affected which defeats the purpose of having 2 grids.

let temp = grid;
grid = next;
next = temp;

These 3 lines may not seem much, but it saves our computers from doing thousands of operations every 30 frames, and reduces those thousand of operations to simply 2 operations.

Now, I had to limit the cells to spawn in a specific area of our canvas, otherwise the calculations would get too much, and wordpress would not be able to handle that many calculations.

let cellSize = 50;
let res = 20;

Resolution is basically how many cubes we want, or in this case we want a dimension of a 20 by 20 by 20 cubes, which is 8000 cubes, and 8000 operations every 30 frames, anymore and the browser would slow down tremendously so 20 was the sweet spot.

for (let x = 0; x < res; x++) {
    for (let y = 0; y < res; y++) {
      for (let z = 0; z < res; z++) {
        // Check if the cell is alive
        if (grid[x][y][z] === 1) {
          // Calculate the position of the cell in the canvas using the res and size as reference.
          let xPos = x * cellSize - (res * cellSize) / 2;
          let yPos = y * cellSize - (res * cellSize) / 2;
          let zPos = z * cellSize - (res * cellSize) / 2;
          push();
          translate(xPos, yPos, zPos);

          // Map scales our rgb colors based on the location so the cube looks like a spectrum.
          let r = map(x, 0, res, 0, 255);
          let g = map(y, 0, res, 0, 255);
          let b = map(z, 0, res, 0, 255);
          fill(r, g, b);
          stroke(0, 50);
          box(cellSize);
          // Take the pointer back to 0,0,0
          pop();
        }
      }
    }
  }

Our coordinate system of the resolution is going to be different to the canvas coordinate system, so with a little bit of math, we could take our raw x y z coordinate and convert them into our resolution coordinates to allow us a proper bounded area.

Finally giving us that beautiful color spectrum, we use the map functions which allows us to scale our resolution with the rgb values, for example at 10 (half way into resolution) the  r value would be 127 (half of 255).

Reflection and Improvements:

Honestly I am particularly happy about how this turned out, I thought it would be quite difficult to implement but it turned out a lot better and easier than I expected it to and I am happy that I went through with it.

A couple ways I would think about improving it is adding a gradient color background, and maybe implement more shapes for cells to be rather than simply a cube.

Week 1 Assignment – Self-Portrait

Concept:

For my self-portrait, I was trying to think of a creative way to display my head. I was mainly thinking of a way that would help me avoid drawing my hijab as I was not too sure how to implement that. I also wanted to force myself to use some creativity since I don’t always get the opportunity to do that in my Computer Science classes. I ended up drawing my head in the shape of a cloud using ellipses. I drew this inspiration from my name, which means a rainy cloud in Arabic. I also added the rain effect in the background to complete my name’s translation. As for the tree, I attempted to draw an olive tree, to represent my Palestinian identity.

Implementation:

I mainly used ellipses and rectangles to create everything in the sketch. I used a rectangle for the tree’s trunk, and circles for the bushes and olives. For the cloud, I drew one ellipse in the centre and then drew multiple ellipses around it, placing them in a way that would create a cloud shape. For the smile, I later realized I could have used the arc() function, but for this specific implementation I drew an ellipse and then put a rectangle over it in the same color as the cloud to create a semi-circle. The nose used the line() function, the eyes are ellipses, and the eyebrows are drawn using the arc() function.

For the rain in the background, I watched this YouTube tutorial on how to add it. Considering my computer science background and my familiarity with using classes and functions, I was able to understand the code and implement it.

Code I am Proud of:

//eyebrows
  stroke(0);
  arc(254, 220, 20, 3, radians(180), radians(355));
  arc(315, 220, 20, 2, radians(180), radians(355));

Despite its simplicity, the eyebrows were definitely the most difficult part to implement. I watched a YouTube video to understand further how it works, as I was a little confused. Through this video, I discovered you can typecast from degrees to radians, which made the function much easier to use since radians are a bit difficult to work with.

//array to store all created raindrops
drops = []

class RainDrop{
  constructor(x, y){
    this.pos = createVector(x, y)
    this.vel = createVector(0, random(8, 11))
    this.length = random(20, 40)
    this.strength = random(255)
  }
  
  //displays each rain drop
  show(){
    stroke(255, this.strength)
    line(this.pos.x, this.pos.y, this.pos.x, this.pos.y-           this.length)
  }
  
  //makes the rain fall
  update(){
    this.pos.add(this.vel)
    if (this.pos.y > height + 100){
      drops.shift()
    }
  }
}

function setup() {
  createCanvas(400, 400);
  p = createVector(random(width), 200)
}

function draw(){
  background(173,212,247);
  
  //loop to create many raindrops and store them in an array
  for (let i = 0; i < 5; i++){
    drops.push(new RainDrop(random(width), 0, 0))
  }
  
  //show each rain drop
  for (let d of drops){
    d.show()
    d.update()
  }
}

The other code snippet I am proud of is the rainfall. Despite using lots of help from YouTube, I was proud of coming up with the idea and having it implemented exactly how I wanted it.

Reflection:

Overall, I am quite happy with the final result and truly enjoyed the process of making this. Despite coding frequently in my CS classes, this type of coding felt different and more enjoyable, less algorithmic. It also feels more rewarding as you create your final sketch because you can see results as soon as you type the code. For the future, I would definitely like to explore more with adjusting strokes so that they only outline certain parts of a shape, especially for parts of the sketch developed by layering multiple shapes.

Self Portrait Assignment – Dina

My Concept:

In this assignment, I attempted to make a self-portrait using only 2D shapes such as circles, rectangles, lines, and arcs. To start, before beginning the assignment or the course itself, I had little to no knowledge of coding, so this was definitely something outside of my comfort zone and a bit challenging. I mainly wanted the portrait to be an almost realistic vision of what I usually look like, so that was what I went for for the portrait. With that being said, I “styled” certain features with a lot of attention to small details that I think I could’ve gone without (which was time-consuming for me, but I thought the portrait wouldn’t be realistic enough without them). I kept the portrait basic with only the face and shoulders showing, and nothing in the background.

Here is the finished result:

The Process:

The first thing I started with was the face. This was probably the easiest task I did for the portrait; I just inserted an ellipse and changed its color using the RGB hex.

I then moved on to the eyes. I was using a series of arcs to create the round shape I wanted. It was difficult at first since I was dealing with radians, and I noticed the unit circle in p5 is actually in reverse.  I kind of gave up midway through with it and switched over to degrees, which was easier for me. But I realized I can’t always rely on degrees, so I switched back to radians, got the hang of it, and the second (right) eye became much easier to make. I added my usual glasses and gave myself some eyebrows as well.

The nose was particularly interesting to make. I initially made the little bulb (circle) of the nose in the center and built everything else around it. I put in the lines of the nose bridge, the surrounding area near the nostrils using arcs, and finally the nostrils themselves. Since the nostrils I could draw were small, I opted for points rather than an arc, since their sketch would be small. I chose this specific structure of the nose to make the sketch as realistic as possible instead of drawing a few lines or a triangle on the side.

The real challenge for me was drawing the lips and hair.

The lips: Since I was going for a realistic look,  I faced a challenge building the upper lip. For reference, I always begin on the left side of the face for all my features. The first thing I did was draw a straight line that led to almost the center of the face, then I created a small arc connected to it in order to resemble a cupid’s bow. The real challenge was recreating this on the right side of the face with the correct dimensions. It was a huge trial-and-error process, where I constantly adjusted the x and y coordinates to recreate the arc’s dimensions as it connected to the line. Once I figured it out, I added a large arc to seal off the upper lip, and I added another larger arc at the bottom for the bottom lip.

The hair: I realize now that there could have definitely been an easier way to draw the hair, but I started it off on a challenging note. I decided to draw singular curls using arcs and just alternating the arcs and their patterns on the whole head. That was unfortunately extremely time-consuming, and I could’ve gone on an easier path. That’s when I realized I could use large circles, put them around the head, and fill them. This made the process much quicker.

Code Highlight:

I am particularly proud of the way the lips turned out as I managed to recreate the same angles and dimensions on both the right and left side of the mouth.

//lips
  //upper lip
  
  line(175, 240, 192, 235);
  arc(193, 236, 10, 2, -PI, 0);
  arc(203, 236, 10, 2, -PI, 0);
  line(205, 235, 222, 240);
  
  arc(198.6, 240, 47, 12, 0, PI);
  
  //lower lip
  arc(198.7, 241, 47, 30, 0, PI);

Reflection:

It was pretty fun to experiment with code, as I had minimal experience with coding before this. There are definitely things I did that made the task much harder when there was a much simpler alternative (such as using circles for the hair, or I could have just built the mouth using 2 arcs).

Overall, I am pretty satisfied with the outcome, especially considering the trial-and-error and effort it took. If there’s one thing I would take away from this assignment, it is to always look for a simpler, less time-consuming way to do things. In the future, I would like to be able to add some interactive element to my work, or even make it more abstract, to get out of my comfort zone and create something different than what I would usually create.

Here is the final code:

function setup() {
  createCanvas(400, 400);
}

function draw() {
  background(180, 130, 190);
  
  print(mouseX, mouseY); 
  
  //neck
  stroke(0, 0, 0);
  strokeWeight(1);
  fill(241, 223, 210);
  rect(155, 288, 90, 66);
  
  //shirt
  //actual shirt
  fill('rgb(210,5,5)');
  stroke(218, 13, 13);
  rect(20, 350, 360, 70, 30, 30, 30, 30);
   //collar
  fill(218, 13, 13);
  stroke('rgb(185,6,6)');
  strokeWeight(20);
  arc(200, 344, 90, 10, 0, PI);
  //collar lines
  stroke('rgb(164,7,7)');
  strokeWeight(1);
  line(200, 340, 200, 359);
  line(208, 340, 208, 357);
  line(216, 339, 216, 359);
  line(223, 339, 224, 357);
  line(229, 339, 230, 355);
  line(234, 339, 238, 355);
  line(240, 337, 243, 353);
  line(244, 336, 250, 353);
  line(247, 335, 253, 349);
  line(249, 335, 255, 342);
  line(191, 339, 191, 358);
  line(184, 339, 183, 358);
  line(175, 339, 175, 357);
  line(168, 338, 166, 356);
  line(161, 337, 159, 351);
  //face and skin
  stroke(1);
  strokeWeight(1);
  fill('rgb(241,223,210)');
  ellipse(200, 180, 200, 240);
    
  
// left eyes successful radians attempt 1
  fill('white');
  arc(150, 150, 50, 30, -HALF_PI, 0);
  arc(150, 150, 50, 30, -PI, -HALF_PI);
  arc(150, 150, 50, 30, 0, PI);
  
  //left eye color
  fill(140, 155, 100);
  circle(150, 150, 25);
   fill('black');
  circle(150, 150, 15);
  
  
// right eyes successful radians attempt
  fill('white');
  arc(250, 150, 50, 30, -PI, 0);
  arc(250, 150, 50, 30, 0, PI);
  
  //right eye color
  fill(140, 155, 100);
  circle(250, 150, 25);
   fill('black');
  circle(250, 150, 15);
 

  
  // eyes attempt radians
    //left eyes shape
 
  //fill('white');
  //arc(150, 150, 50, 30, 0, 2);

  //left eyes shape degrees
  //angleMode (DEGREES);
  //fill('white');
  //arc(150, 150, 50, 30, -90, 0);
  //arc(150, 150, 50, 30, -180, -90);
  //arc(150, 150, 50, 30, 0, 90);
  //arc(150, 150, 50, 30, 90, 180);

  //left eye color
  //fill(140, 155, 100);
  //circle(150, 150, 25);
  
  //right eye shape
  //angleMode (DEGREES); 
  //fill('white');
  //arc(250, 150, 50, 30, -90, 0);
  //arc(250, 150, 50, 30, -180, -90);
  //arc(250, 150, 50, 30, 0, 90);
  //arc(250, 150, 50, 30, 90, 180);
  
  //right eye color
  //fill(140, 155, 100);
  //circle(250, 150, 25);
  
//nose
  stroke('rgb(72,54,24)');
  strokeWeight(1);
  noFill();
  circle(200, 210, 17);
  arc(195, 217, 12, 6, HALF_PI, PI);
  arc(205, 217, 12, 6, 0, HALF_PI);
  
  line(195, 198, 192, 166);
  line(205, 198, 208, 166);
 
 //nostril left 
  strokeWeight(1);
  arc(193, 217, 4, 2, HALF_PI, PI);
  arc(188,207, 6, 16, HALF_PI, PI + HALF_PI);
  
 //nostril right  
  arc(207, 217, 4, 2, 0, HALF_PI);
  arc(212, 207, 6, 16, -HALF_PI, HALF_PI);
  
//lips
  //upper lip
  
  line(175, 240, 192, 235);
  arc(193, 236, 10, 2, -PI, 0);
  arc(203, 236, 10, 2, -PI, 0);
  line(205, 235, 222, 240);
  
  arc(198.6, 240, 47, 12, 0, PI);
  
  //lower lip
  arc(198.7, 241, 47, 30, 0, PI);
  
//eyebrows
  //left eyebrow
  fill('rgb(96,37,37)');
  noStroke();
  rect(132, 120, 42, 6, 0, 3, 0, 0);
  triangle(132, 120, 132, 126, 126, 126);
  
  //right eyebrow
  rect(226, 120, 42, 6, 3, 0, 0, 0);
  triangle(268, 120, 268, 126, 274, 126);
  
//eyelashes
  //left eye
  stroke(1);
  line(127, 144, 120, 138);
  line(128, 141,123, 133);
  line(130, 140, 128, 132);
  line(137, 136, 135, 130);
  line(144, 136, 142, 129);
  line(148, 134, 150, 128);
  line(155, 134, 156, 127);
  line(158, 136, 162, 128);
  line(162, 137, 168, 126);
  line(167, 139, 171, 130);
  line(170, 140, 176, 134);
  
  //right eye
  line(227, 143, 223, 137);
  line(230, 140, 226, 133);
  line(232, 139, 230, 130);
  line(235, 137, 234, 128);
  line(239, 136, 238, 128);
  line(242, 136, 242, 127);
  line(246, 134, 247, 127);
  line(251, 134, 252, 127);
  line(256, 134, 257, 127);
  line(259, 136, 263, 128);
  line(263, 136, 267, 129);
  line(266, 137, 272, 130);
  line(268, 140, 274, 134);
  line(271, 142, 277, 137);
  
//glasses
  noFill();
  strokeWeight(2.7);
  //left lens
  rect(119, 124, 60, 47, 5, 10, 10, 10);
  //right lens
  rect(221, 124, 60, 47, 10, 5, 10, 10);
  //bridge
  rect(179, 139, 42, 1);
  
  
//hair
  fill(96, 37, 37);
  stroke('rgb(96,37,37)');
  strokeWeight(5);
  // arc(199, 77, 60, 10, PI + HALF_PI, HALF_PI);
  // arc(199, 90, 67, 17, PI + HALF_PI, HALF_PI);
  // arc(200, 65, 60, 10, PI + HALF_PI, HALF_PI);
  // arc(200, 78, 67, 17, PI + HALF_PI, HALF_PI);
  arc(240, 70, 69, 19, PI + HALF_PI, HALF_PI);
  arc(262, 88, 69, 19, HALF_PI, PI + HALF_PI);
  arc(240, 109, 69, 19, PI + HALF_PI, HALF_PI);
  arc(262, 109, 69, 19, HALF_PI, PI + HALF_PI);
  arc(227, 65, 60, 10, PI + HALF_PI, HALF_PI);
  arc(227, 95, 67, 17, PI + HALF_PI, HALF_PI);
  arc(243, 72, 60, 10, PI + HALF_PI, HALF_PI);
  arc(229, 70, 67, 17, PI + HALF_PI, HALF_PI);
  arc(290, 102, 60, 20, PI + HALF_PI, HALF_PI);
  arc(247, 81, 67, 17, PI + HALF_PI, HALF_PI);
  arc(290, 84, 60, 20, PI + HALF_PI, HALF_PI);
  arc(276, 91, 67, 17, PI + HALF_PI, HALF_PI);
  arc(278, 114, 60, 25, PI + HALF_PI, HALF_PI);
  arc(260, 95, 67, 17, PI + HALF_PI, HALF_PI);
  arc(267, 67, 60, 10, PI + HALF_PI, HALF_PI);
  arc(279, 69, 67, 17, PI + HALF_PI, HALF_PI);
  arc(272, 113, 60, 10, PI + HALF_PI, HALF_PI);
  arc(284, 126, 67, 17, PI + HALF_PI, HALF_PI);
  // arc(288, 130, 69, 19, HALF_PI, PI + HALF_PI);
  arc(286, 149, 69, 19, PI + HALF_PI, HALF_PI);
  // arc(291, 147, 69, 19, HALF_PI, PI + HALF_PI);   arc(287, 146, 69, 19, HALF_PI, PI + HALF_PI);
  arc(290, 169, 69, 19, PI + HALF_PI, HALF_PI);
  arc(311, 186, 69, 19, HALF_PI, PI + HALF_PI);
  arc(287, 202, 69, 19, PI + HALF_PI, HALF_PI);
  arc(312, 219, 69, 19, HALF_PI, PI + HALF_PI);
  arc(282, 236, 69, 19, PI + HALF_PI, HALF_PI);
  arc(307, 253, 69, 19, HALF_PI, PI + HALF_PI);
  arc(277, 270, 69, 19, PI + HALF_PI, HALF_PI);
  // hair second part copy paste
  arc(303, 183, 69, 19, PI + HALF_PI, HALF_PI);
  arc(320, 203, 69, 19, HALF_PI, PI + HALF_PI);
  arc(288, 168, 69, 19, PI + HALF_PI, HALF_PI);
  arc(307, 253, 69, 19, HALF_PI, PI + HALF_PI);
  
  //circle hair
  circle(260, 109, 27);
  circle(290, 142, 27);
  circle(307, 136, 27);
  circle(274, 88, 27);
  circle(306, 93, 27);
  circle(308, 112, 27);
  circle(263, 67, 27);
  circle(244, 67, 27);
  circle(283, 65, 27);
  circle(302, 182, 27);
  circle(304, 164, 27);
  circle(318, 159, 27);
  circle(313, 133, 27);
  circle(319,109, 29);
  circle(311, 76, 32);
  circle(314,88, 32);
  circle(322, 124, 27);
  circle(325, 97, 27);
  circle(322, 141, 27);
  circle(328, 156, 27);
  circle(332, 141, 27);
  circle(332, 114, 27);
  circle(333, 168, 27);
  circle(335, 187, 27);
  circle(328, 203, 27);
  circle(320, 222, 60);
  circle(322, 242, 60);
  circle(271, 127, 27);
  circle(286, 161, 27);
  circle(211, 65, 20);
  circle(222, 61, 20);
  circle(287, 195, 20);
  circle(285, 205, 20);
  circle(281, 232, 20);
  circle(328, 266, 27);
  circle(339, 272, 27);
  circle(336, 285, 30);
  circle(338, 308, 30);
  circle(340, 326, 30);
  circle(310, 290, 60);
  circle(285, 249, 30);
  circle(283, 267, 27);
  circle(286, 295, 27);
  circle(329, 150, 60);
  circle(343, 183, 60);
  circle(343, 217, 50);
  circle(347, 248, 40);
  circle(354, 270, 50);
  circle(361,232, 40);
  circle(358, 207, 40);
  circle(354, 295, 50);
  circle(351, 316, 40);
  circle(350, 330, 50);
  circle(314, 344, 50);
  circle(286, 325, 50);
  circle(272, 292, 35);
  circle(271, 261, 25);
  circle(223, 77, 27);
  circle(283, 79, 20);
  circle(270, 104, 20);
  circle(224, 91, 20);
  circle(194, 58, 20);
  circle(168, 78, 40);
  circle(177, 56, 20);
  circle(189, 70, 15);
  circle(143, 87, 30);
  circle(124, 101, 30);
  circle(108, 118, 30);
  circle(98, 136, 30);
  circle(107, 136, 30);
  circle(97, 154, 30);
  circle(93, 164, 30);
  circle(98, 178, 30);
  circle(95, 192, 30);
  circle(96, 208, 30);
  circle(105, 185, 30);
  circle(103, 160, 30);
  circle(106, 209, 30);
  circle(104, 225, 30);
  circle(111, 240, 30);
  circle(107, 194, 30);
  circle(114, 251, 30);
  circle(108, 267, 30);
  circle(115, 270, 30);
  circle(139, 97, 30);
  circle(119, 112, 30);
  circle(125, 270, 30);
  circle(121, 288, 50);
  circle(135, 309, 40);
  circle(140, 330, 40);
  circle(139, 350, 40);
  circle(70, 184, 65);
  circle(77, 123, 65);
  circle(72, 233, 65);
  circle(72, 277, 65);
  circle(76, 321, 65);
  circle(109, 314, 65);
  circle(106, 340, 70);
  circle(100, 83, 40);
  circle(129, 69, 30);
  circle(151, 59, 30);
  
  
  
}
  
  
  
  

 

 

 

Week 1 — Self Portrait

1.   Sketch and Code


Code

2.   Overview

For our first assignment, I developed a sketch that is a non-literal self-portrait. The project features a shark – my favourite animal – swimming through a deep-sea environment. The sketch incorporates procedural animation, gradient rendering, and state-based movement logic.

3.   Concept

My goal of this project was to communicate identity through something other than a human face. I chose a shark because it is my favourite animal. In fact, I own a vast collection of shark themed items in real life, so it felt like the most authentic representation of my personality.

4.   Process and Methods

My process began with translating shark anatomy into geometric shapes:

    • I broke the shark down into a main body (ellipse), a tail (triangles), and facial structure (lines and arcs).
function drawSharkSprite() {
  noStroke();
  fill(160); 
  
  // BODY
  ellipse(85, 60, 150, 50); 
  
  // TAIL
  let tailX = 145;   // Local variable
  triangle(tailX, 45, 185, 60, tailX, 75);  // Connection fin
  triangle(195, 95, 145, 45, 185, 60);      // Bottom tail lobe
  triangle(200, 30, 145, 75, 185, 60);      // Top tail lobe
  
  // FINS
  triangle(90, 15, 65, 40, 95, 40);
  
  // GILLS
  stroke(80); 
  strokeWeight(1);
  noFill();
  for(let i = 0; i < 3; i++) {
    let gx = 65 + (i * 5); // Spacing the gills
    arc(gx, 60, 10, 25, -HALF_PI, HALF_PI);
  }

  // --- HEAD DETAILS ---
  noStroke();
  // Teeth
  fill(255);
  for (let tx = 25; tx < 55; tx += 6) {
    let slantY = map(tx, 25, 55, 72, 65); 
    triangle(tx, slantY, tx + 6, slantY, tx + 3, slantY + 7);
  }
  
  // Slanted Eye
  stroke(0); 
  strokeWeight(3);
  line(25, 58, 35, 52); 
  
  // Slanted Mouth Line
  stroke(166, 58, 55); 
  strokeWeight(2);
  line(22, 72, 58, 65); 
}
    • I studied some p5.js functions to avoid static movement of the shark. Instead of the shark simply sliding across the screen, I used the sin() function to give it a bobbing effect.
// Apply bobbing motion using sin() to the Y translation
translate(shark.x, shark.y + sin(shark.yOff) * 10);
scale(2.0); 
drawSharkSprite();
5.   Technical Details
    • To create depth, I wrote a drawOcean() function that uses a for loop to iterate through the canvas height, and using the lerpColor() function transitions from a darker blue to a lighter blue.
    • For the bobbing motion of the shark, I had to make a sort of mathematical function to make it look like the shark is floating in the water. The y-axis position of the shark is updated in the updateShark() function, which makes it so that the values of the y-offset (animation timer) are altered accordingly to be used in the sine function.
function updateShark() {
  shark.x += shark.vx;
  shark.y += shark.vy;
  shark.yOff += 0.05;    // Increment animation timer

  // Boundary checks
  if (shark.x < 30 || shark.x > 220) shark.vx *= -1;
  if (shark.y < 40 || shark.y > 220) shark.vy *= -1;
}
    • To keep the shark within the visible “ocean” and prevent it from swimming off canvas, I implemented a kind of “bounce” motion with a collision system. Since the shark’s movement is controlled by a y and x velocity, its direction can be reversed by simply flipping the sign of the values if the shark passes a certain x or y value.
    • Rather than drawing each tooth and gill individually, I used loops to ensure that, if the shark’s position changes, the features remain aligned..
    • I used translate() and scale() within the draw() loop to allow the shark to move as a single unit without having to update the (x,y) coordinates for every single shape in the sprite.
6.   Reflection

This project was a significant learning curve in managing coordinates. Initially, my shark would fall apart when it moved because the fins weren’t “attached” to the body’s x and y variables. Learning to use translate() solved this by creating a new local coordinate system for the shark. Also, I had a lot of trouble with the “bounce” mechanism of the shark before properly understanding how the coordinates worked. I am proud of my final result, but I hope that in the future I will be able to complete the coding process quicker now that I’ve better grasped some of the built-in functions and coordinate system.

7.   Resources

Assignment_1: Self portrait

I started the work with directly showing my visible form with basic shapes. I did have to look up a new function “curve” to draw my bangs because the shape the “arc” function formed was not good enough. I drew the edge with the “curve” function and then filled in with the arc.

When it comes to portraying myself I thought of including my identity of being a science student apart from my looks, because I love what I do and it is a big part of me and I love to play with the sterotypes. At first I tried to do that by showing myself in a lab coat, but it was sort of difficult because I honestly didn’t know how to draw a lab coat, and I gave up to make a T-shirt looking thing. Then I included the “blowing things up” interation (which might be a bit inappropriate) because of how we used to joke about such things in lab courses.

I got the idea of eye tracing from the “Embarrassed Koala” example and Self-portrait by Pauline Wee that we were shown in class. But because I had the idea of making the explotion animation already, I thout it would be funnier if I made the eyes go in different dirrections and looked stupid, so I flipped through the p5js tutorials and founs the “variables and change” page which I then based my code on (I tried to look at the codes for the embarrased koala example but something was wrong with by browser and the code couldn’t show up). However what I intended to do was somehow different from the original code so I tried a few things and switched things around until the animation worked.

I like the eye tracking and the mouse click interaction codes so I put them below.

//eye movement
  lefteyeX=170+mouseX%width/26
  lefteyeY=150+mouseY%width/24
  righteyeX=230+(-mouseX)%width/26
  righteyeY=166+(-mouseY)%width/23
  //these codes are for normal (non-stupid) eye tracing
  // righteyeX=213+mouseX%width/26
  // righteyeY=150+mouseY%width/23

These are the codes for the eye tracking. I used -mouseX and -mouseY for the right eye so the direction of movement was oppodite from the left (I just instinctively tried this and found that it works so this likely isn’t the most concise code). It looked fun. I also kept the normal eye-tracking codes just in case I wanted to change into a normal look.

if(mouseIsPressed===true) {
    fill('orange')
    stroke('rgb(254,254,0)')
    strokeWeight(3)
  } else {
   noFill()
    noStroke()
    }
    triangle(170,300,160,276,180,292);
    triangle(179,291,185,271,197,290)
    triangle(197,290,232,278,226,298)
    triangle(179,300,157,317,187,312)
    triangle(179,307,182,338,198,312)
    triangle(197,290,206,263,211,293)
    triangle(198,312,207,328,214,304)
    triangle(214,311,232,324,225,300)
    triangle(225,292,241,295,224,306)
    noStroke()
    ellipse(200,300,57,32)
if(mouseIsPressed===true) {
    textSize(25)
    fill('rgb(197,0,0)')
    stroke('rgb(0,0,0)')
    strokeWeight(4)
  } else {
   noFill()
    noStroke()
    }
    text('BOOM!',164,308);

This is the code for the explosion animation on click. The explosion cloud was sort of bad because I couldn’t really figure out how to draw the fancy anime explosions, so it was just composed of an oval and a bunch of triangles. I learned the “if” “else” code from the Conditionals and Interactivity page of the p5js tutorial.

The final results look like this. My friends and I had a good laugh at it.

The space for future improvement that remains is that a lot of my codes are lengthy and verbose. I think this will be improved as I learn mode codes that will be able to show the same effects in a more consice and controlable way. I should also have researched more on the p5js page to perfect the codes I alread know but I was sort of exhausted by figuring out the curves and animations. And I think I lack some creativity in the thinking process as it took me quite some time to think of doing something other than regular portraying. Maybe spending some time brainstorming before I start assignments would help.

Overall I am relatively satisfied with the outcomes and really did enjoy playing around with codes and animations. And I am looking forward to more interesting activities 🙂

Citations:

  • https://p5js.org/tutorials/conditionals-and-interactivity/
  • https://p5js.org/tutorials/variables-and-change/
  • Pauline Wee Assignment 1: Self Portrait https://intro.nyuadim.com/2022/09/01/assignment-1-self-portrait-6/
  • https://p5js.org/reference/