Reading Response: Week 8

Her Code Got Humans on the Moon—And Invented Software Itself

After reading the article on Margaret Hamilton, I can genuinely say her work left me inspired. One of the most fascinating aspects of her journey in software engineering is that the career path was not taught formally at the time; pioneers like Hamilton navigated complex, unprecedented problems relying solely on their resourcefulness and intellect. To me, this makes her story all the more exciting and inspiring. In the article, Hamilton reflects, saying, “When I first got into it, nobody knew what it was that we were doing. It was like the Wild West.” This captures an intriguing concept: how something entirely unfamiliar can eventually grow into a field recognized by the world. It emphasizes the invaluable contributions of innovators like Hamilton, whose dedication transformed this “Wild West” of programming into a modern, booming industry.

Today, what was once unknown is now a leading field, with software engineering being a top choice for bachelor’s degree pursuits. Interestingly, there’s a marked contrast between Hamilton’s era and ours: while she and her peers pursued this path out of passion and a pioneering spirit, the $400-billion industry today is often seen as a gateway to financial stability, with passion sometimes secondary. Despite the fact that software wasn’t even included in the Apollo mission’s budget or official documentation, its impact is undeniably felt across industries today. This article brilliantly highlights the work of women in technology and helps bridge the gender gap by celebrating these overlooked pioneers.

 

Norman, “Emotion & Design: Attractive things work better”

When I first began reading Norman’s essay on “Attractive Things Work Better,” I was initially skeptical, believing functionality should outweigh aesthetics—why prioritize appearance if it compromises performance? Yet, as I delved deeper, I found myself agreeing with Norman’s insights on how attractive designs can enhance usability. One key point he raises is that while aesthetics sometimes need to be sacrificed in contexts where functionality is paramount, there are cases where the two can harmoniously coexist. This perspective helped clarify any initial doubts I had about the essay’s premise.

Norman’s explanation of positive and negative affect, and their impact on user experience, is particularly compelling. His argument on how emotions like anxiety and fear can be leveraged to overcome procrastination, especially when working within time constraints, feels remarkably applicable. At first, I was hesitant about his idea that a product’s utility can be influenced by mood, and the suggestion of owning multiple versions of a product seemed inefficient to me. It felt impractical to rely on mood when selecting functional items, especially considering cases where only the “undesirable” product might be available, potentially leading to a poor experience. This extends beyond the teapot example he provides. However, his quote, “When we feel good, we overlook design faults. Use a pleasing design, one that looks good and feels, well, sexy, and the behavior seems to go along more smoothly, more easily, and better. Attractive things work better,” presents a thought-provoking angle that I acknowledge has merit, though I feel it doesn’t apply universally.

I do disagree with Norman’s claim that color displays offer no additional value compared to black-and-white screens. This seems particularly outdated in today’s digital world; for example, working in design software like Photoshop often requires accurate color information, and relying on a black-and-white display could lead to unintended, muted results. This is just one of several cases where Norman’s argument might fall short.

Finally, Norman’s emphasis on products being affordable, functional, and pleasurable resonates in our present context. This balance is essential, as seen in decisions by major companies like Apple, which might discontinue products like the Vision Pro if they fail to meet these criteria, particularly in terms of affordability.

Week 8: Don Norman and Margaret Hamilton Reading

Both of this week’s readings are the result of developing a complex relationship between aesthetics, degree of complexity and function. Norman in his readings states that inproving the user experience through design is important as it allows users to ignore the minor flaws and bugs by impressing them with creativity. For example, he states that walking the plank at different heights might seem same, but as the height increases, a person might reconsider that. However, if there are some aesthetic design incorporation, then one might choose to do so either due to aesthetic curiosity. However, initially going through Margaret Hamilton’s reading, even I was quite sure that her view is quite in contrast with Norman, as she focusses majorly on error-free, mission-critical functionality while designing software for the Apollo systems. This is because of the risks involved in her work. At the moon surface, a place unknown to humans, anything could go wrong and total preparation was needed. This became clear when just before landing on the moon, the computer overwhelmed with the many tasks on hand, chose to prioritize the main task, and leave the others.

However, Norman’s approach to adaptability in design based on aesthetics goes well with Hamilton’s approach to adaptability in systems. Norman thinks that an appealing interface makes a product more usable for a wider range of people. Hamilton, on the other hand, worked with NASA on the Apollo program and showed adaptability through strict, modular engineering that could handle unexpected edge cases, like when her daughter made a critical error during testing. These similar and different ideas show that Norman’s ideas about beauty make everyday things easier to use, while Hamilton’s high-stakes setting shows how important it is to build for reliability and test things thoroughly, which is an important quality in and of itself for life-or-death tasks.

Week 8: Hugging switch

Idea

For this project, my initial idea was to use a simple interaction as a switch to light up the LED light. I think hugs is the most suitable, because hugs usually gives us warmth and energy, which can symbolically ‘light up’ a light.

Implementation

I create a simple circuit like in this one:

Arduino LED - Complete Tutorial - The Robotics Back-End

I connect the 3.3V to the resistor -> LED -> ground. However, from the 3.3V to the bread board, instead of connecting the wire directly to the resistor, I attach it with a piece of tin foil to my plushy. I also attach another tin foil on my T-shirt, this tin foil is attached to a wire, which is connected to the resistor. Hence, when I hug the plushy, the tin foils touch, completing the circuit and turn on the light.

Video

 

My project implementation was fairly simple, so I did not encounter many problems. Nevertheless, I do have some tips from my first experience working with Arduino. Initially, I tried connecting the wires with regular tape, which didn’t work at all since it isn’t conductive. I then switched to tin foil and wrapped it around the connecting points. Next time, I would use conductive tape or male-to-female wires instead.

Reflection

I enjoyed creating this project. I think the components provided in the Arduino kit make building physical projects much easier. I also try to look at “switch” in a different perspective to come up with a switch which is not only unusual but also has some kinds of meaning.

Week 8: Creative Switch

Short Description:

The project for week 8 asks to design a switch without manually connecting the switch to the power. By looking through some online resources and reviewing the Arduino documents, I created a switch using the distance sensor that detects if there is an object in front of the circuit and two LED diodes that reflect if an object is present. When an object is present, the circuit would light the red LED, and when there isn’t an object, the circuit would light the green LED.

INITAL APPOACH

Going into the project, I knew I wanted to use the sensors that came with the kit because I wanted to learn more about the connections of the components and coding in the Arduino IDE. By searching on Youtube and Arduino documentations, I initially played around with the light senor and was able to create a switch that is dependent on the amount of light that is present in the room. If there was low visibility, the LED would turn on, and if there was high visibility, the LED would turn off. Here is the link I referenced.

However, I felt this design wasn’t creative enough and sill had other components in my kit I want to learn about, so I decided play around with the distance sensor as well.

Light Sensor LED Switch (from Arduino)
The Process

The biggest challenge in the project was understand how the distance sensor worked, but I found a useful Youtube video  that was able to explain the manual to me and walk through the process of connecting each pin to the Arduino board. I understood how to connect the GND and VCC pins, and through the Youtube video, I learned that the trig pin sends out an ultrasonic wave and the echo pin would receive the ultrasonic wave if something was in front the component — similar to bats & echolocation.

Physical Components

After understanding the component, all that was left was to connect my circuit and set up the code to run the project. Below is the photo of my circuit connections from TinkerCAD and physical circuit. Here is a short video (IMG_4697) of how the circuit would work.

TINKERCAD

GREEN LIGHT -- No Object Present

RED LIGHT -- Object Present (i.e: my hand)

The Code

Through the in-class exercise, tinkering with the light sensors, and looking through Youtube videos, I was able to write a short code in the Arduino IDE that makes the circuit function. Below is the main looping function.

void loop() {
// sets the trig pin to a low state 
digitalWrite(trigPin, LOW);
delayMicroseconds(2);

// every 10 mircoseconds sends out a pulse (based off the manual)
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);

// from the echoPin --> returns the time it took the sound wave to travel in microseconds
timeTraveled = pulseIn(echoPin, HIGH);

// calculate the distance (in micrometer)
distance= timeTraveled * 0.034/2;
onDistance = distance;
if (onDistance <= 5){
  digitalWrite(ledRedPin, HIGH);
}
else{
  digitalWrite(ledRedPin, LOW);
}
Conclusion

Overall, I am very happy with how it turned out and being creative with the hand motions of “stop” for red and “go” for green in the circuit. I liked that I was able to learn more about the components and also work in software to make the circuit function the way I wanted it. For anyone else curious or wanting to learn more about Arduino, there are numerous resources and tutorial online that are so helpful, and I recommend anyone else just starting out in Arduino to take a learn and have fun.

Week 8 Reading Response

Emotion and Design: Attractive Things Work Better

Don Norman’s essay, Attractive Things Work Better, focuses on clarifying and further elaborating on some ideas that came into conversation from his piece, The Design of Everyday Things. He works to identify a common ground between beauty and commenting on the importance of both of them. In Everyday Things he  focuses on how designer should prioritize usability, which gave users the impression that it should be prioritized at the sacrifice of aesthetics. However, in this piece Norman explores how the aesthetics of a design can also dictate it’s usability for an individual.

For example, he proposes the idea of walking across a 1 meter wide plank at different height intervals. As the plank gets higher and higher off the ground, the likelihood that an individual would be willing to cross it decreases each time, indicating how the external factors, or appearance, can play a part in whether or not an object is usable. This is important because it further instills how the design process has to be user oriented. Not only does the design of an object itself play a role, but the user’s emotions and the greater context of how an object will be used are extremely relevant. Although finding a happy medium between the two (like discussed in the 3 teapots) is far from easy, it also creates a space for unique design as different creations can adapt to the needs of different people.

Her Code Got Humans on the Moon—And Invented
Software Itself

Robert McMillan’s article, Her Code Got Humans on the Moon, discusses Margaret Hamilton’s career while programming the code that would be used to launch the Apollo. It started out as a discussion of her career progression how she got into this field, and then focuses on her pivotal role as an engineer working on the Apollo project. Hamilton’s story is particularly interesting not only because she was a woman working in tech in the 60s, but also because the work she developed has become part of the foundation for developing software today.

As a mother, she faced lots of doubt and criticism from people who did not understand the important work she was doing. However, in her field, she was respected and welcomed for her contributions to the project. Not only did she defy the societal expectations placed on her, but she excelled in every way. Scientists such as Hamilton are strong examples of how people can go against the odds and make great strides in their field.

Reading Response: Week 08

I began my reading with the piece about Margaret Hamilton, feeling a strong curiosity to learn more about her. It resonated deeply when she mentioned being “one of the guys,” as it reminded me of how, in many cultures, there is a stereotype that only men excel in mathematics, coding, and engineering. When a woman shows talent in these fields, it is often seen as “unusual.” Her story was truly inspiring, highlighting not only her achievements but also the consistency and persistence that made her a role model in engineering. Even though it’s not the first time I’ve encountered gender disparity in STEM, her approach to tackling “imposter syndrome”—which I believe many women in STEM have faced—was a particularly powerful lesson. It’s a reminder to everyone, regardless of gender, to stay focused on their goals and remain committed to what they want to achieve. Her story also brought to mind the movie *Hidden Figures*, which is a great watch for anyone interested in the experiences of women in STEM.

For the second reading, I’ve been brainstorming ideas related to cognitive science and human adaptability. This reading showed how design, when informed by cognitive science, can significantly impact our experiences. For instance, when I visit stores like Miniso, I often find myself drawn to items that are aesthetically pleasing and easy to use. Of course, the definition of “aesthetic” may vary from person to person; for me, it means a sleek, minimal design with soothing colors and user-friendly features. While aesthetic preferences differ, there must be some fundamental principles that apply to everyone. In this context, it’s important to explore the concept of affect and design to understand how we shape our environments and how these designs impact our daily lives. Striking a balance between beauty and usability is indeed the key factor in innovating effective designs and products.

Week 8 – Reading Response

Her Code Got Humans on the Moon

Margaret Hamilton’s story is incredibly inspiring and shows the power of resilience and innovation. She didn’t just contribute to the Apollo moon landing; she redefined what software could be, pioneering a field that would become the backbone of modern technology. Her work at MIT’s Instrumentation Lab, where she developed error-detecting and recovery software for the Apollo missions, was groundbreaking. During the Apollo 11 landing, her code prevented mission failure when the guidance computer overloaded, proving the essential role of well-designed software in complex systems.

What’s especially cool is that Hamilton saw the potential of software when others didn’t. She pushed to make it a core part of the mission’s success, even coining the term “software engineering.” Her success in a male-dominated field makes her story even more remarkable, showing how powerful one person’s vision can be. Hamilton’s legacy is a reminder of how perseverance and challenging norms can lead to innovations that change the world. Her work inspires me to approach challenges with the same confidence and creativity.

Norman,“Emotion & Design: Attractive things work better”

Donald A. Norman’s “Emotion & Design: Attractive Things Work Better” presents a fascinating perspective on the link between aesthetics and functionality. Norman argues that attractive designs aren’t just visually pleasing but also improve usability and user experience. He explains that beautiful, well-designed products trigger positive emotions, which, in turn, make users more open to exploring and interacting with them. This positive emotional state even enhances cognitive abilities, helping people think more creatively and handle challenges more effectively. Norman’s insights challenge the traditional view that function alone is enough, showing that emotional responses play a vital role in how we perceive and use everyday objects.

This idea is compelling because it suggests that design impacts not only a product’s usability but also its psychological effect on users. Norman’s argument that beauty can make products “work better” reveals how critical emotional connections are in design. His work is a powerful reminder for designers to consider aesthetics as an essential part of the user experience, not just an afterthought. It’s inspiring to think of design as a bridge between function and emotion, enhancing both user satisfaction and product effectiveness.

Week 8: Reading Response

Her Code Got Humans on the Moon

When people bring the Apollo project, my mind always goes to Margaret Hamilton because she does not get enough credit for the work she contributed to the success of the mission. As the work of women in male-dominate fields has been systematically forgotten in history, I feel her efforts towards the development of software engineering at NASA has gone unnoticed. Knowing that she had to balance childcare while pursuing her goals (and under considerable amount of pressure), Hamilton’s story is incredibly inspiring to me because it shows that woman have the capabilities to achieve the same academic rigorous fields as men and not limited to housekeeping task.

There’s also the famous photo of Hamiliton next to the code she wrote by hand for the project that makes me realize how far technology has advance within the past decade. Just in the past 60 years, computation has reverted from manually hand punched cards and written embedded design to a completely digitalized software system. As such, for me, the complexity of physical design and software applications has influenced my curiosity to understand how computerI architectural design effects digital computation. Knowing someone like me has been able to understand and develop software before it was mainstream has helped me continue to pursue my goals when times get difficult.

Norman,“Emotion & Design: Attractive things work better”

Norman’s writing talks about the aesthetic which a product embodies affects the way the user perceive it usability and effectiveness. Depending on the circumstances, the product may be more useful, while some not as much. With the three tea pots example, I agree with the theme and that people purchase certain items because of the way it may look and how’d present to other guests or fit in into the task at hand. If the circumstances call for quick/ stress solution, the usability of the product will weigh more than the design. Similarly, if a person has time to appreciate the process, then its design can be extended and shaped uniquely.

I feel all humans have a bias towards what design choice brings them joy, and so in feeling good about the product, the process of using that product would be more enjoyable and any cons are overshadowed by the joy. Whereas, if one is frustrated and disappointed with the product, it sets the expectation that the product would not work as well and one may not put as much effort to finding a solution. I think an interesting point Norman brings up is colored computer screens because if people didn’t like the colored screens modern computers would have remained black and white. Since our eyes perceive color in our everyday lives, it is unsettling to have a black and white scree. As such, I feel our experiences through our senses and experiences define the expectations we lay on the tools we use.

 

 

Midterm Project: The “SpectroLoom”

Concept

For my midterm project, I thought of making something unique, which seemed like art for the casual viewers, but on closer inspection would be seen as a form of data. For this, I decided to make a circular spectrogram, i.e, visualizing sound in circular patterns. That’s when I saw an artwork on Vimeo, which visualized sound in a unique way:

Using FFT analysis, and the concept of rotating layers, I decided to recreate this artwork in my own style, and go beyond this artwork, thus, creating the SpectroLoom. I also decided that since most people sing along or hum to their favourite tunes, why not include them in the loop too?

At its core, SpectroLoom offers two distinct modes: “Eye of the Sound” and “Black Hole and the Star.” The former focuses solely on the auditory journey, presenting a circular spectrogram that spins and morphs in harmony with the music. The latter introduces a dual-layered experience, allowing users to sing along via microphone input, effectively merging their voice with the pre-loaded tracks, thus creating a sense of closeness with the song.

The Code/Science

Apart from FFT analysis, the project surprisingly used a lot of concepts related to “Relative Angular Velocity”, so that I could make the sketch behave in the way I want it to be. Using FFT analysis, I was able to get the amplitude of every frequency at any given point of time. I used these values to make a linear visualizer on a layer. The background canvas is rotating at an angular velocity of one revolution for the song’s duration in anti-clockwise direction, and the visualizing layer is rotating in the opposite direction (clockwise), making it seem that the linear visualizer is stationary because the Relative Angular Velocity is “Zero”. The other user layer, which have the user’s waveform is also doing the same, but uses the mic input as the input source for the FFT Analysis (and is only in the second mode).

Also, once the user finishes the song, they can again left click for restarting the same music. This is done by resetting the angle rotated by the layer to “Zero” after a complete revolution and clearing both song visualization layer and the User input layer.

// Visualizer screen drawing function for "Black Hole and the Star" mode
function drawBlackHoleAndStar() {
  if (song.isPlaying()) {
    background(0);

    // Get the frequency spectrum for the song
    let spectrumA = fft.analyze();
    let spectrumB = spectrumA.slice().reverse();
    spectrumB.splice(0, 40);

    blendAmount += colorBlendSpeed;
    if (blendAmount >= 1) {
      currentColor = targetColor;
      targetColor = color(random(255), random(255), random(255));
      blendAmount = 0;
    }

    let blendedColor = lerpColor(currentColor, targetColor, blendAmount);

    // Draw song visualizer
    push();
    translate(windowWidth / 2, windowHeight / 2);
    noFill();
    stroke(blendedColor);
    beginShape();
    for (let i = 0; i < spectrumB.length; i++) {
      let amp = spectrumB[i];
      let x = map(amp, 0, 256, -2, 2);
      let y = map(i, 0, spectrumB.length, 30, 215);
      vertex(x, y);
    }
    endShape();
    pop();

    layer.push();
    layer.translate(windowWidth / 2, windowHeight / 2);
    layer.rotate(radians(-currentAngle));
    layer.noFill();
    layer.colorMode(RGB);

    for (let i = 0; i < spectrumB.length; i++) {
      let amp = spectrumB[i];
      layer.strokeWeight(0.02 * amp);
      layer.stroke(amp, amp, 255 - amp, amp / 40);
      layer.line(0, i, 0, i);
    }
    layer.pop();
    
    var userSpectrum = micFFT.analyze()

    userLayer.push();
    userLayer.translate(windowWidth / 2, windowHeight / 2);
    userLayer.rotate(radians(-currentAngle));
    userLayer.noFill();
    userLayer.colorMode(RGB);

    for (let i = 0; i < userSpectrum.length; i++) {
      let amp = userSpectrum[i];
      userLayer.strokeWeight(0.02 * amp);
      userLayer.stroke(255 - amp, 100, 138, amp / 40);
      userLayer.line(0, i + 250, 0, i + 250); // Place the user imprint after the song imprint
    }

    userLayer.pop();

    push();
    translate(windowWidth / 2, windowHeight / 2);
    rotate(radians(currentAngle));
    imageMode(CENTER);
    image(layer, 0, 0);
    image(userLayer, 0, 0);
    pop();
  
    currentAngle += angularVelocity * deltaTime / 1000;

    if (currentAngle >= 360) {
      currentAngle = 0;
      
      userLayer.clear();
      layer.clear();
    }

    let level = amplitude.getLevel();
    createSparkles(level);

    drawSparkles();
  }
}

Also, there is the functionality for the user to restart too. The functionality was added via the back function. This brings the user back to the instruction screen.

function setup(){
...
  // Create back button
  backButton = createButton('Back');
  backButton.position(10, 10);
  backButton.mousePressed(goBackToInstruction);
  backButton.hide(); // Hide the button initially
...
}

// Function to handle returning to the instruction screen
function goBackToInstruction() {
  // Stop the song if it's playing
  if (song.isPlaying()) {
    song.stop();
  }
  
  // Reset the song to the beginning
  song.stop();
  
  // Clear all layers
  layer.clear();
  userLayer.clear();

  // Reset mode to instruction
  mode = "instruction";
  countdown = 4; // Reset countdown
  countdownStarted = false;

  // Show Go button again
  goButton.show();
  blackHoleButton.show();
  songSelect.show();
}

The user also has the option to save the imprint of their song via the “Save Canvas” button.

// Save canvas action
function saveCanvasAction() {
  if (mode === "visualizer") {
    saveCanvas('rotating_visualizer', 'png');    
  }
  if (mode === "blackhole") {
    saveCanvas('user_rotating_visualizer', 'png')
  }
}

Sketch

Full Screen Link: https://editor.p5js.org/adit_chopra_18/full/v5S-7c7sj

Problems Faced

Synchronizing Audio with Visualization:
    • Challenge: Ensuring that the visual elements accurately and responsively mirror the nuances of the audio was paramount. Variations in song durations and frequencies posed synchronization issues, especially when dynamically loading different tracks.
    • Solution: Implementing a flexible angular velocity calculation based on the song’s duration helped maintain synchronization. However, achieving perfect alignment across all tracks remains an area for refinement, potentially through more sophisticated time-frequency analysis techniques.
Handling Multiple Layers and Performance:
      • Challenge: Managing multiple graphics layers (layer, userLayer, tempLayer, etc.) while maintaining optimal performance was intricate. Rendering complex visualizations alongside real-time audio analysis strained computational resources, leading to potential lag or frame drops.
      • Solution: Optimizing the rendering pipeline by minimizing unnecessary redraws and leveraging efficient data structures can enhance performance. Additionally, exploring GPU acceleration or WebGL-based rendering might offer smoother visualizations.
Responsive Resizing with Layer Preservation:
    • Challenge: Preserving the state and content of various layers during window resizing was complex. Ensuring that visual elements scaled proportionally without distortion required meticulous calculations and adjustments.
    • Solution: The current approach of copying and scaling layers using temporary buffers serves as a workaround. However, implementing vector-based graphics or adaptive scaling algorithms could provide more seamless and distortion-free resizing.

Week 6: Midterm Project – Superimpose

# Jump To:


Update: Fixed the link, as it turns out, it was broken this entire time!
(wait, so you’re telling me that no one got to play my game? 🙁)


# Introduction & Project Concept

Hey everyone, welcome back! 👋

In this blog, I’ll be talking a little bit about my midterm project. Unfortunately (or fortunately, depending on how much you want them), this blog post isn’t going to be as detailed as my usual ones. Instead, it’ll just be a very cursory overview of a few parts of the project.

So, what is my project? Well, it’s a game where you control a character using your own body (pose detection), and you have to try to fit through the target pose cutouts, similar to the ones you see in cartoons when a character goes through a wall.

Human shaped hole in wall

Though, since the poses are detected using a webcam, it can be hard to get the distance and space required to detect the whole body (and also still clearly see the screen), so instead the cutouts are only for the upper body, which should make it accessible to and playable by more people.

 

 

# Implementation Details

## How it Works (in a Nutshell)

The core mechanic of the game is the player’s pose. Thankfully, this can be detected relatively easily with the help of ml5.js’s BodyPose, which after integrating, lets you know the locations of each of the keypoints. From the 2 models available (Movenet and BlazePose), I choose Movenet, and you can see the the keypoints it detects below.

 

MoveNet keypoint diagram

 

Obviously, the keypoints the camera can’t see won’t be very accurate at all, but the model will still report them. Thankfully, it also reports its confidence, and so you can easily filter out the keypoints which don’t have a certain threshold confidence.

In terms of why I choose MoveNet, it’s because it is the newer model, and was built for faster recognition, which is important in a game like mine (you obviously don’t want players to notice or feel a lag in their movements, which makes it harder to control and less enjoyable) (though to be honest, BlazePose would work fine here too, it would just be a bit slower and laggier). Also, another one of the parameters I specified was the type (SINGLEPOSE_THUNDER), which means that it should only track 1 person, and with slightly higher accuracy.

Anyways, so we got our pose, and I drew it on the screen. Then, the player sees a target pose cutout (which was generated by creating a random pose, and erasing those pixels from the surface), and tries to match it (hopefully, you don’t wanna crash into it!). Once the target is close enough, we check if the player’s pose matches the target cutout’s pose. Now, since it’s nearly (or literally) impossible to match it exactly to the subpixel (in this case, 13 decimal places smaller than a pixel!), especially for every point, we just check whether the pose’s are close enough, by adding a bit of margin to point and checking if the keypoints are within that margin. If it matches, hurray! The player scores a point and the target disappears (only for another one to come out later… 😈, ahem 😇). Otherwise, the target still disappears, but instead of scoring a point, they lose a heart/life. If they’ve lost too many, the game ends.

That basically sums up the main game loop.

Now, this is all implemented in an OOP manner, so there are classes for each scene, and for any objects that would benefit from being a class. So, for scene management, I have an object/dictionary containing a reference to all the scene objects instantiated from their respective classes, and field that holds a reference to the current scene (or alternatively a variable that keeps track of the current scene’s name).

Similarly, I also have an object/dictionary containing all the sounds and images to be used (which get loaded in preload, like the pose detection model). Other than that, the rest is mostly similar to other p5.js projects.

 

## Some Stuff I Liked

This may seem small, but one of the parts I like is my random pose generation. Since it is, you know, a core part of the gameplay (the pose for the target cutouts), I had to get this done fairly well. While I initially thought of using random offsets from a set pose, or even a set of custom poses I did (which would save a lot of time), I knew this solution wouldn’t be the best, and would have several issues (for example, others will likely have a different height and body segment lengths, as well as from different distances and angles to the camera). Instead, my current solution accounts for most of that, and is made it from a system of constraints.

Basically, I first measure some key lengths from the player (such as the distance between the wrist and the elbow, elbow to the shoulder, shoulder to the hip, original y-value of nose, etc). Then I first start by generating random coordinates for the nose, which are in the center 25% (horizontally) of the screen, and within 25px or so (vertically) of the player’s original nose height (btw, so instead of generating several points for the head (eyes, ears, & nose), like the model outputs, I can just treat the nose as the center of the head, which is much simpler and surprisingly doesn’t have almost any drawback for my usecase, so I do that). After this, I get a random angle between -30 to 30 degrees for the shoulder midpoint, and then calculate the shoulders from there (taking into account the player’s shoulder to shoulder length, and nose to shoulder midpoint length). Similarly, I also calculate a random angle in a certain acceptable range, and use the user’s segment length to calculate the exact position. Now, I also want to ensure that no part goes offscreen, or even within 10% of the edges, so I wrap the generation of each part in a do… while loop, which ensures that if a certain part does get generated too close to the edge, it tries again, calculating new positions from new random but constrained values. Additionally, I also don’t want any of the points to be too close to each other (which could especially be an issue with the wrists, both to each other and to the head).

But what if it is literally impossible to satisfy all these constraints for a certain part? Then the program will just keep trying and trying again, forever, which we obviously don’t want. So I keep a track of the number of current attempts, and if it goes above a certain threshold (say 10), then I start from scratch, and return a completely brand new random pose, using recursion (hoping that the new system won’t run into similar situations too many times, which thankfully is the case, as it’s quite rare for it to retry). I also keep a track of the number of attempts for the completely new pose, and if it gets too high, then I just return the default pose (this is more so just to be extra safe, since thankfully, this is almost never going to happen, as there is an extreeeemeeellyyyy small chance that it bails out and fails trying to return a completely new pose, that many times).

So, that’s it! Despite being really quite simple, it’s pretty effective. You can see the code for it, and try it out in the sketch, below (it’s actually a bit unnerving and intriguing how we can associate actions and feelings, just by looking at random poses made with simple rules).

 

// Initialised with some default values (roughly my measurements)
let playerBodyInfo = {
	"nose y-value": 165,
	"nose-shoulder midpoint length": 50,
	"shoulder midpoint-hip midpoint length": 150,
	"shoulder-shoulder length": 90,
	"shoulder-elbow length": 70,
	"elbow-wrist length": 60
}

// lots of other code ... (not relevant for this)

// method within Game class
generateRandomPose(attempts = 0) {
	// Constraints / Ideas / Assumptions:
	//	- Nose should be in the middle 25% (horizontally) of the screen, and near the height of the player's nose originally (similar y-value)
	//	- 0 deg <= midpoint-shoulder-elbow angle (inside one) <= 180 deg (basically, the elbow should be outside the body, extending upwards)
	//	- 45 deg <= shoulder-elbow-wrist angle (inside one) <= 180 deg
	//	- All parts should be within the center 80% (the nose and shoulders don't need to be tested, since they can't reach there anyways)
	//	- Also, parts shouldn't be too close to each other (realistically the thing we need to check for is wrists to each other and the nose)
	//	- First generate nose position (center of head), then shoulders, then so on.

	let outerMargin = 0.1; // 10%, so points should be in the middle 80% of the detection area (webcam feed)
	let minX = webcamVideo.width * outerMargin
	let maxX = webcamVideo.width * (1 - outerMargin)
	let minY = webcamVideo.height * outerMargin
	let maxY = webcamVideo.height * (1 - outerMargin)
	let partAttempts, leftShoulderToElbowAngle, rightShoulderToElbowAngle, leftElbowToWristAngle, rightElbowToWristAngle

	// Initialised with some default values (roughly my measurements)
	let pose = {
		nose: {x: 320, y: 165},
		left_shoulder: {x: 275, y: 215},
		right_shoulder: {x: 365, y: 215},
		left_hip: {x: 295, y: 365},
		right_hip: {x: 345, y: 365},
		left_elbow: {x: 220, y: 255},
		right_elbow: {x: 420, y: 255},
		left_wrist: {x: 200, y: 200},
		right_wrist: {x: 440, y: 200}
	}

	// If it takes too many attempts to generate a pose, just give up and output the default pose
	if (attempts > 100) {
		print('Pose generation took too many attempts, returning default pose.')
		return pose
	}


	// Nose

	pose.nose.x = random(0.375, 0.625) * webcamVideo.width // center 25%
	pose.nose.y = random(-25, 25) + playerBodyInfo["nose y-value"] // y-value +- 25px of player's nose height


	// Shoulders

	let shoulderAngle = random(-PI/6, PI/6) // The angle from the nose to the shoulder's midpoint with origin below (think of a unit circle, but rotated clockwise 90 deg) (also equivalently, the angle from the left to right shoulder, on a normal unit circle). From -30 to 30 degrees
	let shoulderMidpoint = {
		x: pose.nose.x + sin(shoulderAngle) * playerBodyInfo["nose-shoulder midpoint length"],
		y: pose.nose.y + cos(shoulderAngle) * playerBodyInfo["nose-shoulder midpoint length"]
	}
	
	pose.left_shoulder.x = shoulderMidpoint.x - cos(shoulderAngle) * 0.5 * playerBodyInfo["shoulder-shoulder length"]
	pose.left_shoulder.y = shoulderMidpoint.y + sin(shoulderAngle) * 0.5 * playerBodyInfo["shoulder-shoulder length"]
	
	pose.right_shoulder.x = shoulderMidpoint.x + cos(shoulderAngle) * 0.5 * playerBodyInfo["shoulder-shoulder length"]
	pose.right_shoulder.y = shoulderMidpoint.y - sin(shoulderAngle) * 0.5 * playerBodyInfo["shoulder-shoulder length"]
	

	// Hips

	let hipMidpoint = { // The hip's midpoint is really just the shoulder's midpoint, but extended further, so we can calculate it in a similar fashion
		x: pose.nose.x + sin(shoulderAngle) * (playerBodyInfo["nose-shoulder midpoint length"] + playerBodyInfo["shoulder midpoint-hip midpoint length"] + 50*0), // [Nvm, disabled for now] Added 50 in the end, to ensure it's long enough (I'm not using the hips for accuracy or points, but rather just to draw the outline)
		y: pose.nose.y + cos(shoulderAngle) * (playerBodyInfo["nose-shoulder midpoint length"] + playerBodyInfo["shoulder midpoint-hip midpoint length"] + 50*0) // (as above ^)
	}
	
	pose.left_hip.x = hipMidpoint.x - cos(shoulderAngle) * 0.5 * playerBodyInfo["shoulder-shoulder length"]
	pose.left_hip.y = hipMidpoint.y + sin(shoulderAngle) * 0.5 * playerBodyInfo["shoulder-shoulder length"]
	
	pose.right_hip.x = hipMidpoint.x + cos(shoulderAngle) * 0.5 * playerBodyInfo["shoulder-shoulder length"]
	pose.right_hip.y = hipMidpoint.y - sin(shoulderAngle) * 0.5 * playerBodyInfo["shoulder-shoulder length"]
	

	// Elbows

	partAttempts = 0;
	do {
		if (++partAttempts > 10) return this.generateRandomPose(attempts + 1); // If it takes too many attempts to generate this part, just give up and start from scratch
		
		leftShoulderToElbowAngle = random(PI/2, 3 * PI/2) + shoulderAngle // From 90 to 270 (-90) degrees on a normal unit circle (basically 0 to 180 degrees, with the left half of a circle (imagine the unit circle rotated anticlockwise 90 deg))
		
		pose.left_elbow.x = pose.left_shoulder.x + cos(leftShoulderToElbowAngle) * playerBodyInfo["shoulder-elbow length"]
		pose.left_elbow.y = pose.left_shoulder.y - sin(leftShoulderToElbowAngle) * playerBodyInfo["shoulder-elbow length"]
		
	} while (
		minX > pose.left_elbow.x || pose.left_elbow.x > maxX || // Check if it's within the acceptable horizontal range
		minY > pose.left_elbow.y || pose.left_elbow.y > maxY // Check if it's within the acceptable verticle range
	);
	
	partAttempts = 0;
	do {
		if (++partAttempts > 10) return this.generateRandomPose(attempts + 1); // If it takes too many attempts to generate this part, just give up and start from scratch
		
		rightShoulderToElbowAngle = random(-PI/2, PI/2) + shoulderAngle // From 270 (-90) to 90 degrees on a normal unit circle (basically 0 to 180 degrees, with the right half of a circle)
		
		pose.right_elbow.x = pose.right_shoulder.x + cos(rightShoulderToElbowAngle) * playerBodyInfo["shoulder-elbow length"]
		pose.right_elbow.y = pose.right_shoulder.y - sin(rightShoulderToElbowAngle) * playerBodyInfo["shoulder-elbow length"]
	
	} while (
		minX > pose.right_elbow.x || pose.right_elbow.x > maxX || // Check if it's within the acceptable horizontal range
		minY > pose.right_elbow.y || pose.right_elbow.y > maxY // Check if it's within the acceptable verticle range
	);


	// Wrists

	partAttempts = 0;
	do {
		if (++partAttempts > 10) return this.generateRandomPose(attempts + 1); // If it takes too many attempts to generate this part, just give up and start from scratch
		
		leftElbowToWristAngle = random(1.25*PI, 2*PI) + leftShoulderToElbowAngle // random(PI/4, PI) // From 45 to 180 degrees on a normal unit circle. Will be rotated to account for the elbow's existing rotation 
	
		pose.left_wrist.x = pose.left_elbow.x + cos(leftElbowToWristAngle) * playerBodyInfo["elbow-wrist length"]
		pose.left_wrist.y = pose.left_elbow.y - sin(leftElbowToWristAngle) * playerBodyInfo["elbow-wrist length"]

	} while (
		minX > pose.left_wrist.x || pose.left_wrist.x > maxX || // Check if it's within the acceptable horizontal range
		minY > pose.left_wrist.y || pose.left_wrist.y > maxY || // Check if it's within the acceptable verticle range
		dist(pose.nose.x, pose.nose.y, pose.left_wrist.x, pose.left_wrist.y) < 50 // Check if the wrist is too close to the nose ); partAttempts = 0; do { if (++partAttempts > 10) return this.generateRandomPose(attempts + 1); // If it takes too many attempts to generate this part, just give up and start from scratch
		
		rightElbowToWristAngle = random(0, 3/4 * PI) + rightShoulderToElbowAngle // From 270 (-90) to 90 degrees on a normal unit circle (basically 0 to 180 degrees, with the right half of a circle)
	
		pose.right_wrist.x = pose.right_elbow.x + cos(rightElbowToWristAngle) * playerBodyInfo["elbow-wrist length"]
		pose.right_wrist.y = pose.right_elbow.y - sin(rightElbowToWristAngle) * playerBodyInfo["elbow-wrist length"]

	} while (
		minX > pose.right_wrist.x || pose.right_wrist.x > maxX || // Check if it's within the acceptable horizontal range
		minY > pose.right_wrist.y || pose.right_wrist.y > maxY || // Check if it's within the acceptable verticle range
		dist(pose.nose.x, pose.nose.y, pose.right_wrist.x, pose.right_wrist.y) < 50 || // Check if the wrist is too close to the nose
		dist(pose.left_wrist.x, pose.left_wrist.y, pose.right_wrist.x, pose.right_wrist.y) < 50 // Check if the wrist is too close to the other wrist
	);

	return pose;
}
 

(Click inside the sketch, then press any key to generate a new random pose)

 

Another part I liked was the illusion of perspective. So basically, I had to rush this project, so I thought, “Fine, I’ll do it in 2D, that’s much simpler”, and it is, but I still wanted that 3D perspective effect 😅. After playing around for a bit in a new temporary sketch, I found out that if I scale everything from the center, then the 2D sketch appears to have (a very basic, rudimentary, and probably physically inaccurate, but nonetheless, visible) perspective! While the best and fully implemented version is in the final game, you can see that temporary sketch below.

(Click inside the sketch, then press r to restart, or any other key to toggle between the first and second test (now basically just uncoloured vs coloured)… oh also, please, you know what I mean by “any other key”, so don’t press the power button or something 😂)

 

## Some Issues I Encountered

Oh boy oh boy, did I face several issues (as expected). Now, I can’t go over every issue I faced, so I’ll just mention a couple.

First one I’ll mention, is actually the random pose generator mentioned above! Yep, while I did end up getting it working (and well enough that I liked it and included it in my good parts section), and while it is conceptually pretty simple, it still took a fair bit of time (wayy longer than I thought it would, or even still think it needs to), and was a bit tricky, particularly with working out the angles and correct sin and cos transformations (oh, I messed up the signs more than once 😅). In fact, I actually made the sketch above to quickly test out my pose generation! I had written it all “blind” (aka without testing in between), and since it was fairly straightforward, I was confident it would work, but something nagged me to just try it once before the seeing it in the main game, and… yep, it didn’t work. *sighs*. I had to meticulously comment out and retry each portion and work back to a full solution, bit by bit. Fortunately it wasn’t too conceptually difficult (more time consuming than hard, but even the basic trig was a little rough tough at 3 AM), so I succeeded.

Another issue I faced was that the target cutouts, didn’t have any actual cutouts(!), which is, you know, a major issue. Again, I broke out a new temporary sketch to test out my logic, simplifying stuff and working things out. It turned out to be a simple issue of using the screen’s width instead of the webcam’s width, and a few other things. In case you’re wondering, yep, the screen and webcam not only have different resolutions, but also different aspect ratios! It was a bit of a pain initially to get it so they worked seamlessly together, but now I have. The reason behind this, is that some webcams (particularly those on laptops) give a 4:3 image for some reason, and since I need to run a machine learning model (MoveNet, for the poses), they usually reduce the resolution required (otherwise it would take a LOT longer to detect the pose, which would break the game). I wanted the game’s output on the other hand to be a crisp (scalable up or down but ideally) 1920×1080, 16:9 aspect ratio, hence the mismatch.

(It’s not really interactive (besides f for fullscreen), so don’t bother ;) )

Well anyways, that’s it for now (I think I might’ve made it a bit longer and more detailed than originally thought 😅), so without further ado, I present, my ✨midterm project✨! (I should really have a name for it, but I can’t decide 🤦‍♂️)

# Final Result – Midterm Project

While I would normally embed the sketch here, I think you should really open it full screen in a new tab, so follow this link.

# Additional Thoughts & Room for Improvement

With any project, there’s always room for improvement. But for this one specifically, there’s a huge room for improvement 😅 (mainly because I keep seeing new features to implement or ideas to improve it). As usual, there are a bunch of things I didn’t get time to implement or do, ranging from better graphics and polish, to levels and features. I particularly wanted to implement a character that actually goes ahead of you (that you are trying to catch) that “breaks” the walls (causes the cutouts), and also a level that took place in a top secret laboratory setting, where the character is actually stealing some top sneaky info, and so you have to stop them. I kind of really want to continue this and flesh it out (but I also know it’ll not be of much use, and besides, p5 isn’t the best platform to implement this idea, so why waste time here… so anyways *cuts to me working on this months into the future* 😅).   Well, I’m really glad you went through the entire post (and definitely didn’t skip till the final result), but regardless, thanks a lot for reading it (does anyone even read these, or am I just talking to myself? ._. ). Unfortunately, that’s all I’ve got time for, so we’ll have to end it here. Until next time, and enjoy your break!   https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExd3B2ZGduZjBpYXNsa2F6bmxqZjg5dTFjbnE0bWR1czNiZ3FxYzc4OSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/EbOLm8dEE2OrBh68H7/giphy.webp

(psst, do you know what time his watch shows? Ok, fine, it isn’t wearing one, but if it did, it would show) https://i.giphy.com/S8DcNuvt1FUy31LUH6.webp