Week 7 Midterm Project: Sketch and Documentation

Midterm Project

My Sketch

Code Editor | The Sketch

Documentation

Note: This documentation picks up from my previous post “Week 5 Midterm Project Idea,” which outlines my idea and describes the overall structure and concept of my study/work-with me experience. You can see my Demo Midterm here.

My midterm demo covers ~20% of my full project, which isn’t a lot, so creating the remaining ~80% was a brain-racking ride that in hindsight, I think I enjoyed (minus the debugging parts…)! I’ll try to keep this post concise and not too lengthy, but you can also look at the code of my project, which has comprehensive comments that explain why I did the things I did.

How has my concept changed overtime?

Studio Ghibli’s movies always have the best hand-drawn visuals!

When I came up with the concept for the Midterm Progress check, I thought it wouldn’t changed at all. But midway through the process, I realized the graphics don’t look coherent, I don’t find my project too personal/attached to my interest/personality. When studying, I often want to detach from the real world and instead teleport to a fantasy ethereal universe. I’ve always been in awe by Studio Ghibli’s movies and I’ve always thought that if I could live anywhere, I would live in those movies (see this article). So I built my program around that! Each of the portal now takes the user to a scene from a Studio Ghibli movie, and accompanying them is Totoro, my favorite Studio Ghibli character! I was incredibly happy when I found this direction because it made my experience so much more meaningful to ME and also different from the other existing study-with-me apps available out there.

The 4 backgrounds are from the 4 Studio Ghibli movies: From Up on Poppy Hill, The Wind Rises, When Marnie Was There, and Spirited Away. (See References).

My Code Highlight

It’s really difficult to have particular code highlights because I’m just so proud of the whole project as a whole, and every portion has its fair share of difficulties and I’m attached to every line of code…That said, I learned A LOT from creating the countdown timer and drawing the walking Totoro.

For the countdown timer, I found this YouTube tutorial which was really helpful. But theirs was a lot simpler since the countdown starts from when the sketch was first drawn. I wanted mine to only start counting down when my work session starts, and be pause-able, and this was when the complexity starts to creep in.

Basically, millis() always return the number of milliseconds since you run the sketch. For example, to create a 60-minute countdown, you would want to keep track of when millis() reach 60 * 60 * 1000 (to convert from minute to milliseconds). But because I had 3 other screens prior to when my session start, I also have to keep track of how much time was spent on those screens, so that I can subtract that time from millis(). Additionally, because I had a Pause button, it even complicates the math even more. I have to capture how the how much time the sketch has run for until when Pause was clicked. This way, I can resume the countdown timer correctly. (Please see my code comments from line 399 for more specific explanation)

//timer -- EXPLANATION BELOW READ CAREFULLY
  //sessionStart is the milliseconds on home screen, portals screen, menu screen
  //sessionTime is the seconds since the session ellapsed/started. the milliseconds since the session started = milliseconds since the sketch is drawn MINUS time spent on other screens. i convert "the milliseconds since the session started" to seconds by dividing over 1000 and int() essentially gets rid of the decimals
  //In case the timer is paused (detected by global variable paused), we want to stop the timer.
  //when the timer is resume, it should not take into consideration the time we were on "break"
  //hence we either subtract sessionStart from the millis() so far (if we hve never paused) or from pauseStart (which is the no. of millis when paused)
  let now;
  if (paused) {
    now = pauseStart;
  } else {
    now = millis();
  }
  let sessionTime = int((now - sessionStart) / 1000);

  //the countDown takes drink.price (the number of minutes of the timer) times by 60 to convert unit from minutes to seconds. then MINUS "the seconds since the session ellapsed/started."
  countDown = drink.price * 60 - sessionTime;

  //countDown should stop at 0 and never gets negative
  if (countDown < 0) {
    countDown = 0;
  }

  //convert to minutes and seconds
  let mins = floor(countDown / 60);
  let secs = countDown % 60;

let mins = floor(countDown / 60) – Flooring is important to get a whole number for the minutes. This was what happened when I didn’t floor!

 

 

 

For the Totoro sprite, I got it from this library. I thought it would be easy since we did cover sprites in class, but spoiler: I was wrong!

The Totoro sprites I’m using

My approach was to keep the spritesheet and draw only the appropriate selection since image() allows us to specify where from the original image do we want to extract (col * frameW, row * frameH, frameW, frameH). I also kept track of the direction Totoro is facing, which is left, right, standing on the left, or standing on the right. Depending on Totoro’s current direction, I will determine what the next “direction” is. For example, if it is walking facing the right, it should stop when it reaches the edge and stand for 2 seconds before turning and walking left. This was done through conditional statements.

function drawTotoro() {
  //totoroW and totoroH is the width and height of totoro in the original sprite png photo
  //spriteH and spriteW is the width and height that totoro should be drawn on the screen
  //keep the sprite proportional by multiplying it by totoro's width and height ratio
  let spriteH = height * 0.2;
  let spriteW = spriteH * totoroW / totoroH;

  //this is what the sprite sheet looks like
  //STAND (6 frames only)
  //WALKING RIGHT
  //WALKING LEFT
  let row, frames;

  if (totoroDirection == "right") {
    row = 1; frames = 8;
    totoroX = totoroX+ totoro_speed;
    //image coordinates is top left corner, so we want to stop BEFORE it hits width. other wise it will go off screen
    if (totoroX >= width - spriteW) {
      totoroX = width - spriteW;
      totoroDirection = "standright";
      //just when totoro starts to stand, i add 2 seconds to the CURRRENT moment and save it in pauseUntil. when millis() is pauseUntil, totoro should walk again(see later)
      //this is because there is no stopping condition for totoro standing. walking stops when totoro hits either end, but standing doesnt stop unless we tell it to stop.
      pauseUntil = millis() + pause_time;
    }
  } else if (totoroDirection == "left") {
    //same with above
    row = 2; frames = 8;
    totoroX = totoroX - totoro_speed;
    if (totoroX <= 0) {
      totoroX = 0;
      totoroDirection = "standleft";
      pauseUntil = millis() + pause_time;
    }
  } else {
    // else bc  if its not going left or right its standing
    //standing only has 6 frames so if we dont have "frames" or if "frames" is set to 8, totoro will disappear for a split moment because the column 6 & 7 of row 0 in the spritesheet is empty (transparent)
    row = 0; frames = 6;
    if (millis() > pauseUntil) {
      if (totoroDirection == "standright") {
        totoroDirection = "left";
      } else {
        totoroDirection = "right"
      }
    }
  }

  // change frame every 6 draw() calls; since draw() runs 60 times/1 second, totoro changes frame 10 times a second
  //floor basically bc it ensures that we are cutting the spritesheet in proper columns (column 0, 1, 2, 3, etc). other wise it can be 0.1, 0.2, 0.3, ...which cuts totoro :(
  let col = floor(frameCount / 6) % frames;

  image(spritesheet, totoroX, height-spriteH, spriteW, spriteH, col * totoroW, row * totoroH, totoroW, totoroH);

  //totoro should always be walking at the bottom, so y-coordinate stays constant
}

In the original spritesheet, there are 6 frames for standing and 8 for walking. The line let col = floor(frameCount / 6) % frames; calculates which frame Totoro should be in (hence it is the column). I had a bug initially with let col = (frameCount / 6) % frames. Like millis(), frameCount() counts how many times draw() has run; dividing by 6 slows it down so that Totoro’s short legs aren’t moving crazy fast; and modulus wraps the animation again to frame 1 when it has reached frame 8 (col 0 and col 7).

oopsies…

The image aside shows what happens if I didn’t floor(). I referred to Claude for help and debugging, and basically it explains that frameCount / 6 can produce a decimal number (not every number is divisible by 6) so the column/frame, instead of being 1, 2, 3, 4,…can be 2.5, for example. This means that the image displayed will be Totoro’s right half of frame 2 and left half of frame 3. floor() solves that problem!

Another highlight in general is that my program is highly responsive. Almost all of the placement of elements are placed in relative to the dimensions of the canvas. It’s obviously not rocket-science but figuring out where to place something is such a painful process since it often involves trial and error.

playing around with relative positions; i also found drawing rectangle borders around photos to be extremely helpful in debugging
this is less “landscape”

How this was made

For the overall structure, please look at my Midterm Progress post. From screens 1, 2, and 3, (home, portals, menu), it was just a matter of displaying things correctly and making sure the buttons work. Instead of using the button() element, I created all of my buttons manually, meaning, they are primitives with text on top and I detect “clicks” and “hovers” by tracking if mouseX and mouseY are between the boundaries of the “buttons.”

An annoying issue I had at the beginning was that my images became blurry as I resize my window repeatedly (see photo below); my original code is in the photo.

the blurry problem with resizing the original image

This happened because resize() makes direct changes to my original image, and the approach I did to fix this was to create a function that displays the original image in whatever size and dimensions I want to. This way, the original image is kept intact, and when I resize my window, the image is taken from the high-res original.

//the function that fixes this problem:
function drawImg(img) {
  let w, h;
  if (width / height > 16 / 9) {
    w = width;
    h = width / (16 / 9);
  } else {
    h = height;
    w = height * (16 / 9);
  }
  //parameters: img, x,y,w,h
  image(img, (width - w) / 2, (height - h) / 2, w, h);
}

Screen 2 uses a bit of math to display the 4 portals stored in an array into a 2×2 grid (i loops from 0 to 4 exclusive).

let x = (i % 2) * (width / 2); //this is the column!!!
let y = floor(i / 2) * (height / 2); //for row

You can hover the mouse over each portal and see the name and description. This also subtly indicates that the portals are “clickable.” The hover effect is actually really simple as well. Each portal has a corresponding rectangle and some text on top. When mouseX and mouseY is within the boundaries, the rectangle is filled a semi transparent black and the text is white. Otherwise, there is noFill(). So technically, the description is always physically there, but we can’t see it because they were set to be transparent.

For Screen 3, the Menu, the drinks are objects created from the class Drink. Each drink contains a name, an image (address), a price, etc. When displaying the menu, I loop from 0 until the length of the menu (which is 8) and use the incrementing loop variable to determine the y position of the drink on the menu. This way, the menu displays a list of drinks, where each drink is listed on after another.

for (let i = 0; i < drinksMenu.length; i++) {
    //height * 0.22 is the starting y-position of the first drink; the drinks are 
    //placed height * 0.085 lower than another, so I made use of the variable i
    drinksMenu[i].displayOnMenu(i * height * 0.085 + height * 0.22);
  }

For the Session screen (Screen 4), it’s the main and whole point of the experience, so it’s not a surprise that it was the hardest to built.

I first started with the to-do list. The to-do list was structured as a 2D array, each task is stored as [name of task, true/false (completed)]. I had the most difficulties figuring out how to get the input. I initially wanted to use input(), but I couldn’t figure out how to get the input() on top of the canvas. I even tried creating a separate canvas using createGraphics() to make the input bar relative to that new canvas, so I can easily move it around, but in the end there was a lot of trouble so I had to find another way.

doesn’t work :(( drawSession() is also called repeatedly so there were a bunch of input()

I asked Claude how else I could have done this, and Claude told me to use prompt() as an alternative. prompt() is a JavaScript function and it basically asks for an input using a pop-up. I implemented that, it works, however, one thing I absolutely dislike about it is that it ruins the fullscreen experience. When you’re on fullscreen and you add a task, the prompt will take you out of fullscreen mode (because it’s a browser thing) and then when you’re done, you have to enter fullscreen mode again. It’s might be a minor UI/UX thing, but overall, it makes the experience a bit less seamless.

The drink is a static PNG and they are watercolor, hand-drawn graphics form Canva (see References). Initially, I wanted to draw the drinks myself and create variations of them so that when the countdown reaches halfway, the drink becomes half empty, and when the countdown finishes, the drink is empty. However, due to the time-constraint, I couldn’t do that, which is a shame. I decided to opt for the Canva graphics in the end since they still look cohesive with the rest of the program (I think this would be better than me drawing low-effort graphics that looks crappy).

For the timer and pause button, look at the “My Highlight Code” section.

The music button is also fun to make, and I honestly think that music adds the liveliness to my program. I store the tracks in an array sounds[], and when the user clicks on the music button, the function changeMusic() is called. song = (song + 1) % 5 allows the change of music to happen: it goes to the next index (song) in the array, and loops back to index 0 if needed.

//array sounds hold NULL at index 0 (since it corresponds to no music), indexes 1 to 4 holds songs
function changeMusic() {
  //always check if it's the NULL one (doesnt work if it is)
  if (song != 0) {
    //first stop wtv is playing
    sounds[song].stop();
  }
  //then change song to the next element (index). we do modulus so that it WRAPS around back to index 0 when the current index is 4.
  song = (song + 1) % 5;
  //anything other than 0, play it and loop it
  if (song != 0) {
    sounds[song].play();
    sounds[song].loop();
  }
}

The end “X” button resets certain variables to its default, stop the music, and take the user back to the Home screen.

Reflection

Most often times when I create and produce something, I try my best to make it something that I can genuinely be proud of. I feel like this project looks simple, but my process was far from that. I spent a ridiculous amount of time on this project, and I’m overall really happy with the final outcome. It’s a huge success in the sense that I stayed quite close to my original plan, I only had to tweak and abort a few features.

The thing that bugs me the most is probably the prompt() that shows up when you click on “ADD TASK.” I’m pretty sure there is another and better way to do it, but I couldn’t figure it out at the moment. If I had more time, I would definitely try and draw the drinks myself to create the “sipping” effect.

Other than that, I would say that I have achieved what I wanted to do with this project. I’m glad I made the change to a Studio Ghibli-themed experience, it feels, now, like something I would actually use to study for my other midterms…I also learned a lot during the process, from new methods and functions to silly mistakes or syntax errors that result in hours of debugging! I don’t think I’ve ever built something this complex in such a short amount of time, but I think it the end it’s definitely worth it.

References

Visuals and Graphics

NONE OF MY GRAPHICS ARE AI-GENERATED WORK. The backgrounds are actual stills from Studio Ghibli.

Fonts
Audio
Tutorials and References
Documentation Illustrations
How Artificial Intelligence were used
  • ChatGPT refined the descriptions for the 4 portals (not their names, since their names allude to the locations in the movie)
  • Claude helped me debug my code (particularly the timer + pause, and the Totoro spite). Claude did not write the code for me, it explains why and where my code was not working so I can find a way around that.
  • Claude introduced me to the prompt() element and taught me, very basically, how to use it.

Artificial Intelligence was not used in any other part of this project.

Week 5 Reading Assignment

This week’s reading leans a bit more towards the technical side, and I honestly think that it brings a new perspective to the table about the intelligence of computers. As the reading suggested, “a computer, without additional programming, is unable to answer even the most elementary questions” that humans can! The concept of computer vision is so interesting because I often understand it the way my vision works (looking at the content & understanding it) where in reality, computers process data pixel by pixel, character by character.

The reading also identifies the 3 low-level algorithms that help computers “see:” frame differencing, background subtraction, and brightness thresholding. At this point of the reading, I found myself asking 1) are these techniques used at the same time and 2) how exactly is this different from human vision? Do we also do this in a faster, almost automatic manner that it seems “natural,” or we do process data completely differently?

I especially appreciate the example of Limbotime. By piecing the illustrative photo and the author’s description, the theoretical aspects discussed prior in the reading makes a lot more sense. I also really admire the amount of details that goes into an arguably quite simple game (identification of body, tracking of body, position relative to imaginary lines, etc.)

Despite all of the above, the creepiest part that stood out to me is the issue of surveillance, particularly the project Suicide Box at the Golden Gate Bridge. Overall, surveillance is complicated because it directly interferes with privacy, consent, etc. But for Suicide Box especially, the system is recording very very sensitive and “negative” scenes. It’s also notable that in the same period, the system counted 17 instances of suicide, while official reports had 13. Does this suggest that the system has flaws with its vision and identification? Could it mistaken humans and other objects? Or is it so accurate that it reveals the flaws of official reports? I think it’s so disturbing and creepy overall, but this reading definitely confronts the potential dangers and unethical aspects of computer vision that I overlooked.

Week 5 Midterm Project: Idea & Progress Documentation

My concept

For my midterm project, I plan to build an experience called “productive portals.” Basically, it’s a work/study with me experience where the user and choose a portal to transport to, and the program will have creative interactions (explained below).

The home screen provides a brief description of the experience and a button that says “choose portal”. When clicked on, 4 portal options pop up, and the user gets to choose 1 of 4 portals.

This takes the user to the study location they chose. A “drinks menu” pop up, where the user can choose from a variety of drinks. The fancier the drink is, the longer the work timer is (for example, if they choose a strawberry matcha, they will work for 90 minutes).

After this, they will enter the main working screen. The main working screen contains an illustration of the drink they chose, a timer counting down, and a to-do list where the user can add the tasks they need to complete. There will also be 3 buttons on the side:

  • End session: the study session will end and takes the user back to the home screen
  • Pause: timer will pause
  • Music: choose 1 of 4 or 5 ambience background music
a wireframe design on Canva…

Structure and Implementation

I also plan to use OOP and create classes for Drinks, Portals, and Tasks. I’m not entirely sure yet…!

The user will mainly use mouse-press for interaction that requires decision making, and they will need to type with keyboard to enter tasks into the to-do list.

The most intimidating portions for me are the to-do list and the menu, I also found some resources that I think would be helpful to look at:

Demo version

I created a demo version which contains the structure of the first 3 screens (including the menu): see code; see full screen.
Note: since this is the demo version, my only focus was the structure and functionality. In other words, please disregard the aesthetics :))

Week 4 Reading Assignment

The same technology that simplifies life by providing more functions in each device also complicates life by making the device harder to learn, harder to use. This is the paradox of technology and the challenge for the designer.

I really enjoyed this reading and it’s especially nice to read such a thorough and well-articulated explanation for something I’ve experienced in everyday life without having the words to describe them. This chapter immediately reminded me of this hilarious reel I saw the other day. While this isn’t my original video, I’ve had experiences with fancy washrooms being so confusing with their water faucets and sometimes even the flush.

My favorite thing about this reading is that it’s so easy to connect to real-life situations. The concept of Norman’s door is such a strong symbolism for the distinction between bad and good designs. What’s interesting is that in many cases, the solution to the problem is very simple: a handle for pull doors, and a flat plate for push doors. As we can derive from this chapter, though, bad designs either 1) does not consider functionality at all or 2) overconcern aesthetic that it takes priority over functionality.

My problems with doors have become so well known that confusing doors are often called “Norman doors.”

Although many of the examples from the chapter are of physical things, I find it super helpful to apply this principle of design on interactive media projects. It makes me think about when we design applications, websites, or games, when can we rely on affordances and signifiers, and when do we truly need written instructions?

Whenever you see hand-lettered signs pasted on doors, switches, or products trying to explain how to work them, what to do and what not to do, you are also looking at poor design.

It makes sense to me that visual cues should ideally guide the users, but I also wonder where exactly we should draw the line between written instructions and symbols and affordances. At the end of the day, the designers of the “bad” designs mentioned in this reading probably believed that their design made sense when they first designed them. I personally think that we shouldn’t completely abandon written instructions, because sometimes they are the only/most effective way to communicate something clearly! In my future IM projects, I hope to be able to step out of my perspective and imagine being a user interacting with my creation for the first time. Although this is easier said than done, this would be a really helpful step to refine my designs.

Week 4 Assignment: Flights and Airports Visualization

My Sketch

Important Note: The dataset is HUGE and sometimes it takes a couple of seconds for the sketch to load. Please do not SPAM the checkboxes, and kindly wait 1 to 2 seconds before clicking/unclicking a checkbox.

My concept

I was inspired by the demo of world population we did in class and I really wanted to try something similar yet still distinct. The moment I thought of flight pathways, I knew I had to do this. I love traveling and flying and I knew that visualizing all the flight pathways could create the outline of the world map, and create a neural-network-looking artwork.

One of the difficulties I had was searching for a comprehensive dataset. I simply wanted the coordinates of the starting point and coordinates of the ending point for every flight, but most datasets I found organized the data a bit differently. I asked ChatGPT to help me find a dataset that has the structure I was looking for, and it suggested me this dataset on DataBazaar, which I end up did using.

what my sketch looks like with both checkboxes checked!

My highlight code

The highlight of my code is my checkboxes and how they function. There are 2 checkboxes, one to show the airports (as dots) and one to show the flight routes (lines). You can check none, one, or both. The checkboxes are in the setup() function, and I essentially want the map to change depending on which box(es) are checked.

check_routes = createCheckbox("SHOW ROUTES", true);
check_routes.changed(() => {
    loop();
  });

Normally, I could have if check_routes.checked() in the draw() function to indicate what I want to happen based on the status of the boxes. However, I have a noLoop() at the end of the draw(), otherwise it would be too computationally expensive to have dots and lines being mapped out and drawn 60 times every second. But because I had noLoop(), the draw() function only draws once and stops, which means that checking the checkboxes does not have any effects. This was also a major bug in my code, so I had to ask ChatGPT for help and it taught me about the changed() function! So, when the checkboxes are changed (aka clicked/unclicked), the sketch draw() will resume in looping. It has a noLoop at the end though, so technically it only loop once.

How this was made

My dataset has 4 columns: the longitude and latitude for the start, and the longitude and latitude for the end (destination). I extract each column into their respective arrays: start_lng, start_lat, dest_lng, dest_lat.

The next step was to find the maximum and minimum of the latitudes and longitude. Because I had 2 arrays for latitudes and 2 arrays for longitude, I had to repeat this process 3 times for each value. For example, in the code below, I find the maximum latitude value in start_lat and the maximum latitude value in dest_lat, then comparing them to get the actual maximum latitude. This process is then repeat for min_lat, max_lng, min_lng.

let max_start_lat = max(start_lat);
let max_dest_lat = max(dest_lat);
let max_lat = max(max_dest_lat, max_start_lat);

Then, with these values, I can draw the flight lines and airport endpoints using the map() function. I loop from 0 to the numRoutes (the number of rows = the number of flight paths in the dataset) and in every iteration, I extract the coordinates into x1, x2, y1, y2. These x and y variables are then used to draw a line that resembles one flight route.

if (check_routes.checked()) {
    //loop every entry in numRoutes
    for (let i = 0; i < numRoutes; i++) {
      //save the coordinates in a variable and print the line connecting the points
      let x1 = map(start_lng[i], min_lng, max_lng, 0, width);
      let y1 = map(start_lat[i], min_lat, max_lat, height, 0);
      let x2 = map(dest_lng[i], min_lng, max_lng, 0, width);
      let y2 = map(dest_lat[i], min_lat, max_lat, height, 0);
      line(x1, y1, x2, y2);
    }
  }

The block of code to draw airports is also very similar; instead of drawing the line, I draw 2 circles with radius of 2, whose centers uses (x1, y1) and (x2, y2). There was one bug, though. For some reason, the p5 editor is unable to handle the amount of iterations in the drawing of the circles. If the loop had i increment by 1 (i++), the p5 would only draw some of the circles. When I changed it to i+=10, I reduced the number of circles it has to draw by 1/10, and ironically it displays more circles than when I had i++. I decided to keep it at i+=10, and I think this bug is most likely because my dataset has ~66K rows, which is a lot to handle.

when I had “i+=10”
when I had “i++”

Reflection

I think it looks incredibly cool, and I personally like how abstract the sketch is with just the routes mapped. I was a bit worried that my assignment would be quite similar to what we did in class initially, so I incorporated the checkboxes as a way to 1) challenge myself and take the visualization a step further, 2) encourage meaningful interactivity, and 3) create an opportunity to learn about new things. Compared to the demo population example you have us in class, drawing the flight pathways is also quite different. Most importantly, I learned new things in the process of this assignment, which is always a win :))

If I had more time, I would love to have a search bar where the user types in the code of the departure and arrival airport (e.g.: DXB, JFK, LAX), and the sketch would make that route a different color if a direct flight is available. I think that this is feasible, since the original dataset does come with airport codes. It would be quite complex though, since it involves searching, I think.

Resources I Used

Original Dataset: https://www.databazaar.io/datasets/71f62319-b8c7-42f1-be03-c7eb69308335
My edited dataset (via Google Sheets): https://docs.google.com/spreadsheets/d/187gIhSrk3LXHEgqG_BxJ3_Gz9WVtgrXmvUJ0kJV333s/edit?gid=2055795158#gid=2055795158
https://p5js.org/reference/p5/createCheckbox/

How Artificial Intelligence was used:
  • to find my original dataset
  • suggested to me the changed() function which I used for the checkboxes in setup()

Artificial Intelligence was not used in any other part of this assignment.

Week 3 Reading Assignment

I feel like I have used the term “interactive” so many times to describe artworks but I’ve never really sat down and defined what it means to me. The author identified the same issue, and I really like how they view it as a “conversation” — a “cyclic process in which two actors alternately listen, think, and speak.”

This reading also allows me to reflect critically about the difference between interactivity, reactions, participation, etc. I think I have used them interchangeably before, and looking at the author’s classification, my two biggest questions are:
1). Who decides what category something belongs to? is it perhaps the creator, the person who experiences it, or someone else?
2) Can something fall into 2 categories at the same time?

I find this distinction useful, but I also want to complicate a claim that Crawford made. The author strongly suggests that printed books are not interactive, for no matter what the audience do or say or think, the content of the page does not change. Though this is true, I also think it exposes a gap in Crawford’s definition of interactivity. Crawford insists that the emotional engagement one experiences when reading a book is a reaction, not an interaction. Is this really true, I thought. In murder mystery books, for example, many authors leave clues and hints and expect their audience to try to solve the mystery along with the reading process. Isn’t this an interaction? Actively trying to solve the book like it’s a puzzle. Even at this exact moment, I’m having internal conversations about what Crawford wrote in this first chapter of “The Art of Interactive Design.” I’m contemplating, finding examples, finding contradicting details, trying to justify my thoughts—I think that encapsulating all of that in the term “reaction” will not do it justice. Maybe, it’s some degree of interaction, I suppose.

We tend to think of interactivity as a Boolean property…but why not think of interactivity as a continuous variable with relative measures…we might speak of interactivity as high, moderate, low, or even zero, thus solving our problem with the subjective nature of interactivity.

– Chris Crawford, page 6

On to the guiding question of how I will create a strong interactive system in my p5 sketches. I think this is a big question overall, but at the moment I’m thinking of meaningful interactions. For example, instead of mouse-pressed or key-clicked interactions, perhaps I can have more complex ones that make sense to my project. Also, decision-making can be a really fancy way of interaction. For example, in the in-class example we had with the floating balls appearing as we click on the mouse, the new ball appears at mouseX, mouseY instead of at a pre-defined coordinate. Then, the user can choose where to add a new ball. I also think that a strong interactive system makes it clear to the audience that this artwork is interactive. This can be done through explicit instructions or strong visual cues.

Week 3 Assignment: vy’s garden using OOP

My Sketch

Please view the full sketch here.

My concept

I really love how much freedom we have for this assignment! I came up with an idea to create a garden of flowers, called “vy’s garden.” I wasn’t inspired by any particular sources but I’ve always wanted to create a game of planting flowers =)

My idea was to have the user click on their mouse to plant a new flower, which would have random characteristics. This way, every click comes with curiosity and surprise. When all the flowers in the garden are unique and random, each final garden feels personalized and special.

My highlight code

My canvas size is 700x400, and my garden can hold up to 7 flowers (the number of flowers it can hold is 1/100 of the width of canvas). After the 7th flower, if the user continues to click their mouse, all of the current flowers will be removed and they can plant up to 7 new flowers.

// if garden.length > 7
  if (garden.length > width/100) {
    //loops until garden is empty
    while (garden.length > 0) {
      garden.pop();
    }
    x = 100;
  }

In my mousePressed() function, after creating a new flower and adding it to the array, I have an if-statement checking the size of the garden. If it is larger than (width/100) (in this case, 7), then remove all the flowers in the array garden by using pop() until the garden is empty. After that, I reset x to 100, this is the starting x-coordinate of the first flower.

How this was made

I created a class Flower, which serves as the template for my flowers! The flower objects are stored in the garden array.

one of the first variations in the process
each flower is a combination of 5 circles, a line, and sometimes, a triangle

Each flower can be one of two species (tall/short), have a random petal color, a random center circle color, and a random number of leaves. The details are as follow:

  • 2 possible species: short & petals creating an “X” shape OR tall & petals creating a “+” shape.
  • 4 possible petal colors
  • 4 possible flower center colors
    let petals = ["#FF6B6C", "#f1b709", "#FC814A", "#9ecb15"];
    let flower_center = ["#5f247b", "#095314", "#AD1F2D", "#202C59"];
  • 3 possible variation of leaves: no leaves, left leaf, right leaf

There are 96 possible variations of a flower (2×4×4×3). For a garden of 7 plots, where flowers can be repeated, and the order of the 7 plots do matter, there are 967 possible unique gardens. That is equal to 75,144,747,810,816 aka ~75 trillion variations!!
To put 75 trillion into perspective, it is:
~2.5 times the amount of cells in the human body
~38 times the number of galaxies in the universe
~642 times the number of humans ever been born in the human history

To create the randomization, when I instantiate a new flower, its arguments are randomized. For example, int(random(2)) gives 0 or 1, which will indicate the species. Similarly, petals[int(random(0, 4))] returns the string hexcode at a random integer index of the array petals.

//constructor(species, x, petal, center, leaves)
  /*
  2 possible species: 0 for the tall one and 1 for the short one
  x: x-coordinate of the flower
  petal: a random color from the array petal, accessed by indexing at a random integer index
  center: a random color from the flower_center array
  leaves: 0 = no leaves, 1 = right leaf, 2 = left leaf
  */
let newFlower = new Flower(
    int(random(2)), //random(2) is exclusive of 2
    x,
    petals[int(random(0, 4))],
    flower_center[int(random(0, 4))],
    int(random(0, 3))
  );

Each new flower is then pushed into the garden array and the entire garden is being drawn in the draw() function.

Reflection

I’m super proud of this artwork since I’ve wanted to create a digital garden of my own for sooooo long! I love how interactive it is, and I’m really amused at how many possible variations of flowers and gardens there are!

I’m also incredibly happy that this simple assignment involves many of the concepts we learned in class. I used for loops, while loops, conditionals, the random function, multiple arrays (garden, petals, flower_center), and most importantly, objects and classes. If I had more time, perhaps I would also want to give some movement to the flowers when they appear rather than just abruptly appearing like it is right now. For example, I could have it “rise” from the ground by controlling with speedy. But overall, I’m super happy with what I came up with already!

Resources I Used

https://p5js.org/reference/p5/text/
https://p5js.org/reference/p5/textStyle/
https://p5js.org/reference/p5/textFont/

No Artificial Intelligence was used in the making of this sketch or its documentation.

Week 2 Reading Assignment

Original video: https://vimeo.com/45851523
Quotes and ideas referenced from the video comes with a timestamp in purple: (MM:SS)

When I first started making things in code…every line of code was precisely determined by myself and it produced something exactly as I wanted it to be.
– Casey Reas, (0:22)

I connect to this so incredibly well and in fact, it’s quite visible in my Week 2 assignment (hehe). I feel like randomness and chaos often comes with the feeling of “uncontrollable mess” which is intimidating. However, after watching this video, I have the impression that with art, perhaps randomness can produce what is beyond (and even better than) my imagination.

Piet Mondrian. Broadway Boogie Woogie. 1942–43

One of the most eye-opening and interesting facts I learned from the video is on Piet Mondrian’s grid paintings. I’ve come across these so many times, and they look like one of those “anyone can do that” paintings at first. I, personally, felt emotionless every time I saw them, so I was surprised when Reas said that these paintings contain brush strokes of “emotional, human-quality (21:54).”  I learnt that Mondrian was indirectly inspired by boogie-woogie music, a repetitive, persistent, and repeating bass on the piano.

texture of Mondrian’s painting; screenshot from video

Yet, if looked closely in person, one can notice the visible and thick brush strokes, and the wobble at the edges of the line (21:10). When I learned that Mondrian did not use masking tape for clean edges and wanted to, instead, retain the human texture, my view of his grid paintings completely changed. 

Listening to the lecture also reminds me of something I was told as a kid: that real artists don’t use erasers, rulers, or drawing compasses. I never really understood why, but as a child, I thought that it’s because professionals don’t make drawing “mistakes.” After this video lecture, I’m reminded that art is more about embracing humanness, the imperfections, the nuanced and delicate strokes.

Above all, it’s never a blind chance; it’s a chance that is always planned but also always surprising.
– Gerhard Richte (18:30)

On to the guiding question of “where is the optimum balance between total randomness and complete control,” I think it’s difficult and arguably impossible to come up with the “optimum balance,” since it varies by artists and what they’re comfortable with. However, with me personally, I think incorporating random elements would certainly add some “spice” to my work, as it provokes curiosity and excitement. Some of the ideas I came up with ironically involve controlling the randomness:

  • Random combinations of colors from a list/array of colors that are on-themed.
  • Shapes/elements appearing/moving at random coordinates/speeds within the canvas boundaries
  • Random number of shapes/elements but there is a lower limit and upper limit.

This way, I can have some “randomness” within my sketch without making them total chaos.

Resources I Used

[photo] https://www.moma.org/collection/works/78682

Week 2 Assignment: Artworks, patterns, and loops

My Sketch

My concept

When I first heard that the artwork can use loops to create a pattern, my mind jumps straight to the iconic terrazzo tiles that are used on the sidewalks of the streets in Hanoi, my home city. These tiles come in different patterns, and every Vietnamese, including myself, strangely finds nostalgia in these tiles because it is what we grew up with. Vietnam’s terrazzo tiles have a much deeper history than I could ever imagined: In the early 1990s, a project was set out to get rid of the bumpy concrete pavements. Inspired by the European, particularly Venice’s floral tiles, tile designers and suppliers decided to simplify them into designs of geometrical shapes. There are so many designs, each iconic in their own way, and I decided that for this assignment I wanted to draw one of these patterns on p5.

there are many terrazo tiles designs!
4 tiles together

 

 

 

My highlight code

for (let col = 0; col < numOfTiles; col++) {
    for (let row = 0; row < numOfTiles; row++) {

      push();
      if ((col%2==1 && row%2==0)||(col%2==0 && row%2==1)) {
        translate((col+1)*tileSize, row*tileSize);
        scale(-1, 1);
      } else {
        translate(col * tileSize, row * tileSize);
      }
      drawTile(numOfTiles);
      pop();
    }
  }

I draw each individual tile in the function drawTile() then in the main draw() function, I call drawTile() for a total of 16 times to create the 4×4 grid as shown above. I was especially proud of this section of code because instead of having to manually call drawTile() 16 times, I used a nested for loop to optimize the process! Within each iteration, I use a conditional statement to check whether I should flip the tile (basically creating a mirrored version of it) based on its position in the grid. The next section, “How this was made,” goes into detail of the algorithm and why I used certain functions. It also links to the tutorials and reference pages I used.

How this was made

This assignment was way harder than I had initially expected! I first explored on a default 400×400 canvas, where I created one single tile. I have also embed the sketch for it here, as this was a crucial step in my process. I tried to be as accurate as possible, while also challenging myself to use the different p5 primitives. While I could have just drawn the indented lines, I decided to challenge my approach and draw the shapes and blocks instead. I primarily used squares and arcs to create the geometric shapes.

Sketch for 1 single tile (hardcoded on 400×400 canvas):

One of the things I was being super cautious about was with the numbers and math. Symmetry is an important feature, so I had to make actual calculations with the coordinates to ensure everything aligns. I used nested for loops to create the small squares in the top-left and bottom-right, and also gave them some border-radius (this is different from the actual tiles).

the blue part is the stroke; the green part is the fill. (I put green for demonstration but in the actual sketch I set it to noFill())

Then, for the curves in the top-right and bottom-left, I used arcs that are one quarter, make them transparent in the middle (noFill) and gave them a big stroke to create the curves.
For the curved triangle in the corners, they are actually squares, but an arc sits on top of each square, basically hiding half of it and giving it a curve hypotenuse. Overall, since the single tile is mirrored over y=-x, it was not difficult, I just had to be careful with the placement and order of the primitives.

the red arcs sit on top of the top-right and left-bottom squares. later I set it to match the color of the background, hence “covering” half of the square

However, the real challenge really came when I wanted to put multiple tiles together to make a pattern. When I was drawing the single tile, I was hard-coding the coordinates for a 400×400 canvas. Therefore, I won’t be able to resize or place them next to each other easily. Then, I had to create a duplicate file of my single tile, and manually change the values to be responsive to the width and height of the canvas (it was a tedious process but the code wasn’t extremely long because I had used for loops to avoid redundancy). You can find the sketch for the responsive tile here. The hard-coded and responsive tile looks exactly the same on a 400×400 canvas, but they are massively different in the numbers and variables I used to draw them.

Once I have made the single tile responsive to the canvas dimensions, the next thing on the checklist is to put several of them together to create a pattern. I put all the primitives in a function drawTile(), then in draw(), I call drawTile() when I need to draw a tile. Notice that the pattern is created with the tiles and its flipped version (symmetry at y=-x and y=x). In order to create the flipped variations, I used scale(-1,1) to flip the tile over the y-axis. I watched The Coding Train’s “How to use scale() in p5.js” tutorial and also referenced the corresponding p5 reference. I also notice a pattern with the tiles that are to be flipped. If we do a 4×4 grid of the tiles, and have columns and rows numbered from 0-3, the flipped tiles would either:
– have even-numbered row + odd-numbered column; or
– have odd-numbered row + even-numbered column
I used an if statement here, evaluating odd/evenness using modulus 2. If it is one of the 2 scenarios above, I call scale(-1,1) before I call drawTile().

In order to place the tiles at the correct spots on the grid, I used the translate() function which allows me to set a new origin of (x,y) instead of the default (0,0). I watched this tutorial and looked at the p5 reference page for this function. One thing I noticed in the Youtube tutorial is that you have to use push() and pop() before and after translate(). While I haven’t fully understood how these 2 functions work, it appears that they sort of “resets” the origin to (0,0) after every tile is drawn, otherwise the next time I call translate(), it would be relative to the (x,y) arguments I last passed. I used nested for loops to create the 4×4 grid, then calls drawTile() after translating the origin.

Reflection

This tile is one of the more complicated designs but what made it especially so difficult is the diagonal symmetry. I took a lot more time than I had initially expected, but ultimately in the end, I think the pattern turned out really nice.

I basically had to do 3 steps: a tile on 400×400 canvas, making the coordinates relative & dynamic to the canvas dimensions, and printing the pattern. I feel like my current approach is definitely over-complicated and takes more time and effort than it needs to be. However, my experience with p5 is quite limited, so I tried my best with the knowledge and resources I have.

Also, if I had more time, I would want to play around with the colors. Maybe I can make the pattern changes color every couple seconds or when the end-user clicks on the canvas. I think that would add a very cool layer of interactivity to this otherwise static pattern.

Resources I Used

https://www.youtube.com/watch?v=pkHZTWOoTLM&t=56s
https://p5js.org/reference/p5/scale/
https://www.youtube.com/watch?v=maTfm84mLbo
https://p5js.org/reference/p5/translate/
[photo] https://topmatstore.vn/gach-terrazzo-p4501.html
[photo] https://gachngoihanoi.com/gach-via-he-terrazzo/gach-via-he-terrazzo-mat-nai-do-post239.html
all other screenshots and figures are my own

No Artificial Intelligence was used in the making of this sketch or its documentation.

Week 1 Assignment: Self-Portrait on p5.js

My Sketch

My concept

For this project, I wanted to depict a regular “headshot” of myself using different p5.js functions. My preference for aesthetic leans toward minimalism and simplicity, so I tried to make the portrait look “clean” and “slick” instead of super realistic. I chose a pink-green color scheme since one of my favorite drinks is strawberry matcha, and I want to incorporate parts of my personality into the portrait.

Ultimately, my goal for this project was to explore a variety of features on p5.js, so I practiced with the different primitive shapes we learned in class, as well as researched some other functions on my own.

My highlight code

As ironic as it may sound…my favorite part about my portrait is my background! Initially, I had a 1-color pink background, but it seemed too boring and I wanted some personality! I decided to create a checkered background with 2 different shades of pink. In order to create this, I worked with nested for loops, running variables i and j until the canvas width/height and having them as the (x,y) coordinates for the colored squares. I had previous coding experience with python and java, so I only really had to refer to p5 reference for the correct syntax.

//print checkered background
  //loop 1 -- prints odd numbered rows
  for (let i =0; i<width; i += 100 ) {
    //ROWS--loop until width of canvas, increment by 100 each time to "skip" a block as it goes from left to right
    for (let j =0; j<height; j += 100 ) {
    //COLUMNS--loop until height of canvas, increment by 100 each time to "skip" a block as it goes from top to bottom
      fill("#ffb8bb")
      noStroke()
      //the top x,y coordinate is the i & j that increments
      square(i,j,50);
    }
  }
  //second loop-print even numbered rows
  /* similar logic to previous loop except this time i start i from 50 to create the "checkered" effect*/
  for (let i =50; i<width; i += 100 ) {
    for (let j =50; j<height; j += 100 ) {
      fill("#ffb8bb")
      noStroke()
      square(i,j,50);
    }
  }
the bean-shape mouth!

I’m also really proud of how I made the mouth. I knew I wanted a blob for an open-mouth instead of a smiley line because it looks cuter (and also less creepy). However, a blob isn’t a primitive shape and therefore I couldn’t figure out how to create the mouth the way I had envisioned. Then, it suddenly clicked that I can totally make it a super small arc with a huge stroke (I felt like a genius), which is exactly what I did to achieve the darker red mouth.

//mouth -- blob is created using an small arc with huge stroke
  stroke("#a20b0b")
  strokeWeight(25)
  noFill()
  arc(200,240,50,50,(PI/3),(2*PI/3));

How this sketch was made

the bangs (transparent green)

I started by planning out what would be the bottom-most layer (excluding the background), which would be the hair. The hair is simply 2 overlapping ellipses, which creates the middle hair part. Then, my head looked like an egg so I had to put some bangs. I did this by having 2 lines with a huge stroke, covering the top diagonally. The photo aside illustrates this, with the bangs being a transparent green for demonstration.

The eyes are just many circles placed on top of one another, while the eyelid is a half-circle arc (no fill) that sits behind the white of the eye. Because most of the features on this portrait (not my face :)) are symmetrical, it was quite easy to duplicate something twice. The nose is, also, just a small arc positioned in the center of the face.

This was what I wanted to do with the quadrilateral

The neck has two parts to it: a rectangle and the collar part, which is the bottom half of a circle. In between the neck and the collar is a green shirt, which I created by using a rectangle. I was really contemplating on whether to use a rectangle or a quadrilateral, since with a quadrilateral, I can make the slant of the arms, which I prefer a lot more. However, I then realized that the corners of these shapes are too sharp, which makes the body boxy and therefore less coherent. This could be fixed with border-radius, which allows for rounded corners, but there were only available as a parameter for rectangles, which I decided to stick with at the end.

function wink() {
  //first I cover the original eye using an ellipse the blends with the face
  fill("#d9a582");
  noStroke();
  ellipse(230,195,60,60);
  stroke("#341504")
  strokeWeight(3)
  noFill();
  //then the 2 lines
  line(245,175,212,191);
  line(245,210,212,191);

  //after wink() runs for 500 milliseconds, openEye() runs for the rest of the setInterval which would be 2.5 secs in this case
  setTimeout(openEye, 500);
}

I created a function wink() to also play around with animation. This function draws a large ellipse on top of my right eye in the same color as my face, which covers it. Then, 2 lines created the “<” which indicates a wink. Then, I put setInterval(wink, 3000) in my setup function so that it winks after 3 seconds. But then, I stumbled upon a bug as the person wasn’t winking when I ran the code at all! I then realized that the draw function, which currently contains all of my code, immediately redraws the face on top of my winked eye. Since the portrait was completely static at this point, I moved all of my drawing to setup(). This solves the issue, but then my person stays winked. I tried solving this issue by having another function that draws the original eye, called openEye() and add setTimeout(openEye, 500) to my wink(). What this does, essentially, is have the person wink for 500 milliseconds and then the normal eye appears for 2.5 seconds, and then another interval happens where it winks again. I’m not sure if this is the most optimized way to create this animation, but I had fun playing around with the facial expressions for sure!

Reflection

Overall, I’m really happy with how my portrait turned out! I think I have, in general, achieved the “clean” look I was aiming for. I also love how colorful and vibrant the sketch is, I feel like it captures my energy really well. One thing I feel like I could improve if I had more time is to make it more like me! This would be a bit of a challenge since I don’t like anything that is too detailed, but I’d like to put myself through those challenges some time! I could try having little blushes/freckles, or a different accessory (since I never wear bows).

Also, a mistake I made in the beginning was not determining the appropriate canvas size. I kept it at the default size, 400×400. I only realized after sketching the head and the hair that this wouldn’t leave me enough space for the body, so I had to change the height to 500 pixels. In future projects, I would like to avoid this mistake as I could have to manually shift everything up/down/left/right by a certain value (which is kind of a hideous thing to do :)).

Resources I Used

https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout
https://www.w3schools.com/jsref/met_win_setinterval.asp
https://p5js.org/reference/p5/for
https://p5js.org/reference/p5/arc/
https://coolors.co/f58484-ffb8bb-d9a582-12631c-68d34a

No Artificial Intelligence was used in the making of this sketch or its documentation.