Midterm Project – Inkling of Life

For the midterm project, my inspiration was the “Bounce” game. The game play is similar to the original Bounce game: the character moves and tries to avoid the dangers. I wanted to challenge myself, so the whole game is built using “Vector” shapes. I went through Daniel Shifman’s THE NATURE OF CODE book’s first chapter, “Vector,” and his videos on Vector on YouTube (1, 2, 3) to grasp the basic understanding of the vector shape. As I used vector for all the shapes, all the shapes were preplanned, and it took a good amount of time to figure out the exact configuration.

The game is designed with multiple screens, each offering a different part of the game experience. The game structure is as follows:

  1. Title Screen: Initially, the game will open to a title screen (scene 1), where players can see the game’s title and interact with inkdrop animations. I’ll include a play button and an options button, making sure to check for player interactions to navigate to other parts of the game.
  2. Game Play Screen: Moving onto this scene (Scene 2), I plan to have the main character move, attack, and be displayed on the screen. The gameplay will involve navigating through jump blocks, floors, jelbys (damage elements), and other elements, with the camera following the character to keep them in focus. I’ll implement a jumping function for character movement, and as players progress, they’ll encounter a moving door and health items that affect the character’s health, which will be displayed and updated on the screen. If the character’s position falls below a certain threshold, it’ll trigger a transition to the dead screen (scene 3), whereas reaching a higher position will lead to the winner screen (scene 4).
  3. Dead Screen: If the character dies (scene 3), I’ll display a game over screen where players can see their failure and have options to interact with, possibly to retry the level or go back to the main menu.
  4. Winner Screen: For those who conquer the levels (scene 4), I’ll cover the screen with a rectangle and display a winner message, indicating their success. This screen will also include interactions, possibly to proceed to the next level or return to the main menu, marking the `won` variable as true to track the player’s progress.
  5. Options Menu: Lastly, an options menu (scene 5) will be included to allow players to customize their game experience. I’ll handle this functionality through an `optionsStuff()` function, providing various settings for players to adjust according to their preferences.

The UML diagram:

 

I wanted to highlight the code for the floor design of the game. It was really challenging to find the sweet spot, as it could have made the game too easy or too hard. For this, I could not find any references. So I played the game itself to understand where to put the holes and hills.

//Normal Floors
function setupFloor() {
  floors.push(new Floor(-500, 0, 525, 600));   //0
  floors.push(new Floor(0, 500, 605, 100));    //1
  floors.push(new Floor(600, 425, 250, 400));  //2
  floors.push(new Floor(849, 325, 150, 500));  //3
  floors.push(new Floor(995, 500, 1000, 1000));//4
  floors.push(new Floor(1850, 375, 100, 500)); //5
  floors.push(new Floor(1950, 500, 300, 500)); //6
  floors.push(new Floor(2249, 370, 200, 500)); //7
  floors.push(new Floor(2448, 500, 200, 500)); //8
  floors.push(new Floor(2647, 240, 150, 500)); //9
  floors.push(new Floor(2796, 500, 450, 500)); //10
  floors.push(new Floor(3245, 400, 200, 500)); //11
  floors.push(new Floor(3444, 500, 400, 500)); //12 
  floors.push(new Floor(4050, 500, 500, 500)); //13
  floors.push(new Floor(4549, 380, 150, 500)); //14
  floors.push(new Floor(5050, 480, 100, 500)); //15
  floors.push(new Floor(5149, 500, 1000, 500));//16
  floors.push(new Floor(6148, 365, 200, 500)); //17
  floors.push(new Floor(6550, 400, 150, 500)); //18
  floors.push(new Floor(7100, 550, 900, 500)); //19
  floors.push(new Floor(7999, 150, 200, 500)); //20
  floors.push(new Floor(8198, 500, 880, 500)); //21
  floors.push(new Floor(8977, 400, 150, 500)); //22
  floors.push(new Floor(9126, 450, 570, 500)); //23
  floors.push(new Floor(9825, 0, 20, 700));    //24
  floors.push(new Floor(9844, 450, 900, 500)); //25
  floors.push(new Floor(9705, 0, 20, 300));    //26
  floors.push(new Floor(9687, 430, 175, 500)); //27
  //This One is Special
  floors.push(new Floor(9705, 200, 20, 150));   //28
}

function Floor(x, y, w, h) {
  this.pos = createVector(x, y);
  this.w = w;
  this.h = h;
  this.dis = function() {
    fill(100, 100, 100);
    rect(this.pos.x, this.pos.y, this.w, this.h);
  }
}

//Pipes
function PipeParts() {
  this.pos = createVector(0, 0);
  this.dis = function() {
    //Pipe1 down
    fill(100, 100, 100);
    rect(this.pos.x, this.pos.y, 180, 30);
    rect(this.pos.x + 300, this.pos.y, 140, 30);
    rect(this.pos.x + 439, this.pos.y, 20, 20);
    rect(this.pos.x + 459, this.pos.y, 20, 10);
    fill(220, 100);
    rect(this.pos.x + 180, this.pos.y, 120, 100);
    fill(250, 200);
    rect(this.pos.x + 270, this.pos.y + 10, 5, 35);
    rect(this.pos.x + 270, this.pos.y + 50, 5, 15);
    rect(this.pos.x + 200, this.pos.y + 70, 5, 15);
    rect(this.pos.x + 200, this.pos.y + 85, 15, 5);
    fill(100, 100, 100);
    rect(this.pos.x + 170, this.pos.y, 20, 100);
    rect(this.pos.x + 290, this.pos.y + 0, 20, 100);
    rect(this.pos.x + 160, this.pos.y + 100, 160, 20);
    rect(this.pos.x + 156, this.pos.y + 103, 10, 14);
    rect(this.pos.x + 319, this.pos.y + 103, 5, 14);
    //Pipe2 up right
    //rect(this.pos.x + 9687,this.pos.y + 430,175,30);
    //rect(this.pos.x + 9825,this.pos.y + 200,20,500)
    fill(220, 50);
    rect(this.pos.x + 9725, this.pos.y, 100, 430);
    fill(250, 200);
    rect(this.pos.x + 9735, this.pos.y + 300, 5, 20);
    rect(this.pos.x + 9735, this.pos.y + 330, 5, 10);
    rect(this.pos.x + 9810, this.pos.y + 400, 5, 20);
    rect(this.pos.x + 9800, this.pos.y + 417, 15, 5);
    rect(this.pos.x + 9735, this.pos.y + 50, 5, 50);
    rect(this.pos.x + 9735, this.pos.y + 120, 5, 20);
  }
}

function movingDoor() {
  if (triggers[3].triggering() && floors[28].pos.y < 300) {
    floors[28].pos.y += 2;
  } else if (floors[28].pos.y > 200) {
    floors[28].pos.y += -2;
  }
  if (triggers[4].triggeredy()) {
    chars[0].pos.y += -6.5;
    triggers[4].trigged = true;
  }
}

 

I had the chance to do play testing with 3 users due to time constraints. The most common feedback was that I should have added sound effects for the damages. Two of the users felt the level was hard considering it’s the first and only level. They liked the vector animation for the jumps and movements.

As I played along to create the game, I lost sight of the difficulty. I should have play-tested while designing the floors. I wanted to make the game have infinite levels. Unfortunately, I could not finish it. The current gameplay is limited to only 1 level. In the future, I want to make the floors and scene more generative. Based on the feedback, I also want to improve the sound.

Week 5 Midterm Progress

For the midterm, I wanted to create a simple game using OOP concepts. The basic concept of the game is similar to that of Super Mario: a character moves and tries to avoid dangers. Rather than using sprites, I wanted to create all the aspects of the game fully using code. During the other assignments, I didn’t try out the createVector function. However, going through Daniel Shifman’s THE NATURE OF CODE book’s first chapter, “Vector,” and his videos on Vector on YouTube (1, 2, 3) I am fully convinced I can build all the game elements using the createVector function. My plan is to rely on vector shapes for complex game elements. This will also make my calculations easier.

I’m designing a game with multiple scenes or screens, each offering a different part of the game experience. Here’s how I’m planning to structure it:

  1. Title Screen: Initially, the game will open to a title screen (scene 1), where players can see the game’s title and interact with inkdrop animations. I’ll include a play button and an options button, making sure to check for player interactions to navigate to other parts of the game.
  2. Game Play Screen: Moving onto this scene (Scene 2), I plan to have the main character move, attack, and be displayed on the screen. The gameplay will involve navigating through jump blocks, floors, jelbys (damage elements), and other elements, with the camera following the character to keep them in focus. I’ll implement a jumping function for character movement, and as players progress, they’ll encounter a moving door and health items that affect the character’s health, which will be displayed and updated on the screen. If the character’s position falls below a certain threshold, it’ll trigger a transition to the dead screen (scene 3), whereas reaching a higher position will lead to the winner screen (scene 4).
  3. Dead Screen: If the character dies (scene 3), I’ll display a game over screen where players can see their failure and have options to interact with, possibly to retry the level or go back to the main menu.
  4. Winner Screen: For those who conquer the levels (scene 4), I’ll cover the screen with a rectangle and display a winner message, indicating their success. This screen will also include interactions, possibly to proceed to the next level or return to the main menu, marking the `won` variable as true to track the player’s progress.
  5. Options Menu: Lastly, an options menu (scene 5) will be included to allow players to customize their game experience. I’ll handle this functionality through an `optionsStuff()` function, providing various settings for players to adjust according to their preferences.

The UML diagram would be something like this:

// Preload function to load images
function preload() {
  title = loadImage('title0.jpg')

  imgs[0] = loadImage('imgs0.jpg')
  imgs[1] = loadImage('imgs1.jpg')
  imgs[2] = loadImage('imgs2.jpg')
  imgs[3] = loadImage('imgs3.jpg')
}

// Variables
let scene // 1=title, 2=level, 3=dead, 4=winner, 5=options
let title
let imgs = []
let play
let options
let winner
let optionsmenu
let won
let buttonTO;

// Arrays
let chars = []
let cameras = []
let jump
let healths = []
let floors = []
let triggers = []
let healthItems = []
let jelbys = []
let jumpBlocks = []
let inkdrop = []
let pipeParts = []

// Setup function to initialize the canvas and objects
function setup() {
  createCanvas(windowWidth, windowHeight);
  scene = 1

  play = new PlayButton()

  options = new OptionsButton()

  gameOver = new gameOverScreen()

  winner = new Winner()

  optionsmenu = new OptionsMenu()

  won = false

  buttonTO = 1

  jump = new Jump()

  for (let i = 0; i < 2; i++) {
    inkdrop.push(new Dropplet(198, 220, 3, 3, 7))
  }

  inkdrop.push(new Dropplet(282, random(320, 350), 1, 4, 3))

  inkdrop.push(new Dropplet(435, 530, 2, 4, 7))
}

// Draw function to render the game
function draw() {
  noStroke()
  background(200, 200, 200)

  buttoning()

  // This is the code for the Title screen.
  if (scene === 1) {

    image(title, 0, 0)
    noStroke()

    for (let i = inkdrop.length - 1; i > 0; i--) {
      inkdrop[i].draw()
      inkdrop[i].move()
      if (inkdrop[i].isDone()) {
        inkdrop.splice(i, 1);
        inkdrop.push(new Dropplet(198, 220, 3, 3, 7))
      }
      if (inkdrop[i].isDone2()) {
        inkdrop.splice(i, 1);
        inkdrop.push(new Dropplet(282, random(320, 350), 1, 4, 3))
      }
      if (inkdrop[i].isDone3()) {
        inkdrop.splice(i, 1);
        inkdrop.push(new Dropplet(435, 530, 2, 4, 7))
      }
    }

    play.dis()
    play.check()

    options.dis()
    options.check()

  }

  // This is the code for the 1st level.
  if (scene === 2) {

    if (!triggers[4].triggeredy()) {
      chars[0].move();
      chars[0].attack();
      chars[0].disAttackUn()
    }
    chars[0].histo();
    chars[0].dis();

    for (let i = 0; i < jumpBlocks.length; i++) {
      jumpBlocks[i].dis()
    }

    for (let i = 0; i < floors.length; i++) {
      floors[i].dis();
    }

    for (let i = 0; i < jelbys.length; i++) {
      jelbys[i].dis();
      jelbys[i].damaging(chars[0], healths[0])
      jelbys[i].move(jelbys[i].P1, jelbys[i].P2);
      jelbys[i].hitpoints();
    }

    if (!triggers[4].triggeredy()) {
      chars[0].disAttackOv()
    }

    cameras[0].cameraMan(floors, chars[0])

    if (!triggers[4].triggeredy()) {
      jump.jumping(floors, chars[0])
    }

    pipeParts[0].dis()

    movingDoor()

    if (triggers[0].triggered(chars[0])) {
      triggers[0].trigged = true
    }

    for (let i = 0; i < healthItems.length; i++) {
      healthItems[i].dis();
      healthItems[i].counter()
      healthItems[i].healthup(chars[0], healths[0])
    }

    healths[0].dis()
    healths[0].damage()

    if (chars[0].pos.y + chars[0].h > height * 1.5) {
      scene = 3
    }

    if (chars[0].pos.y + chars[0].h < -100) {
      scene = 4
    }

  }

  // This is the code for the Dead screen
  if (scene === 3) {
    gameOver.dis()
    gameOver.check()
  }

  // This is the code for the Winner screen
  if (scene === 4) {
    fill(227, 227, 227);
    rect(0, 0, width, height)
    winner.dis()
    winner.check()
    won = true
  }

  // This is the code for the Options menu
  if (scene === 5) {
    optionsStuff()
  }

}

 

So far, I am done with the different scenes. Since the beginning, I have started coding the different scenes separately. I will focus on the gameplay now. As I am using vector elements, I am confident that creating the gameplay will not be difficult. However, I realized if I can create the game with something like infinite level with each level creating different pattern for the floor would be more challenging.  So, I will try to work on this later.

Week 5 Reading Reflection

The exploration of computer vision in interactive art, as discussed in the article, highlights its transformative impact on the way artists recreate, manipulate, and explore physical reality. What struck me most was the early adoption of computer vision, dating back to the late 1960s, underscoring the longstanding curiosity and experimentation within the art community towards integrating technology with creative expression. The article not only provides a historical overview but also introduces simple algorithms and multimedia tools that democratize computer vision for novice users unfamiliar with the field.

The potential of computer vision to convey complex sociopolitical themes was both surprising and occasionally unsettling. For instance, Rafael Lozano-Hemmer’s “Standards and Double Standards” uses this technology to critique surveillance culture, illustrating the power of computer vision to metaphorize our realities. Conversely, the “Suicide Box” project reveals the ethical considerations inherent in employing such powerful tools, highlighting the necessity of thoughtful engagement with technology’s capabilities and impacts.

I was particularly intrigued by the emphasis on adapting computer vision techniques to the physical environment. This approach not only challenges creators to think critically about the interaction between technology and space but also broadens the concept of interactivity in digital creation. Learning about the specific strategies employed by artists to optimize their work for different environments reinforced my appreciation for the nuanced relationship between art, technology, and the physical world.

Week 4 Reading Reflection

I enjoyed reading Don Normam’s “The Design of Everyday Things.” After reading Crawford’s definition’s of interactivity in “The Art of Interactive Design,” it was necessary to understand the fundamentals of interactions themselves, which go beyond the definition and importance. To me, the fundamental principles of interactivity were very well put in Don Norman’s book. I liked the text, probably because the examples were very grounded in our day-to-day interactions.  The way Norman advocated for human-centric design seemed very similar to Myron Krueger’s philosophy that technology should serve human needs and desires (What Should You Wear to an Artificial Reality?). This also prompted the benchmark of calling a design good or bad.  The reading really helped me to understand the importance of intuitive design. I always wondered why all the cars, phones, and computers looked similar.  Norman’s examples of the learning curves of the users and intuitive design pretty much answered that question. However, I felt the examples lacked the presence of age groups. It would have been interesting to explore how intuitive design changes through time and generations. Norman’s illustration of the distinction between affordances and signifiers was very thought-provoking for me, as I had never explored the dimension of interactivity this much.

 

Assignment 4

For this assignment, I wanted to recreate the Matrix movie’s Neo sees the matrix for the first time in the scene, in which we see everything in green from Neo’s perspective.

I wanted to create the scene using the transcript of the movie itself. So, I quickly downloaded the transcript from open source. While looking for any tutorials online, I found Daniel Shiffman’s tutorial on creating images with ASCII text. I followed this tutorial only to complete the assignment. Following the tutorial, I started with creating the still image attached to this post with the movie transcript. I quickly realized that due to the presence of unwanted characters (numbers, :, etc.),  the visualization was not looking great. I decided to clean up the transcript according to my needs. I used p5.js to remove any unwanted characters from the data. However, following the tutorial, I could not replicate the brightness using empty/smaller ASCII text, as I was using sentences to create the visualization. So, I decided to manipulate the brightness of the text itself, and it worked. However, moving into the video loop, the code was not working smoothly. I realized that as I was using a large string (3300 lines), the array became too big to loop and refresh in the draw loop. I had to cut down the data to the first 70 lines of the transcript to accommodate that.

 

// draw function
function draw() {
  background(0); // setting background as black

  let charIndex = startIndex;
  // calculating the width and height of the cells
  let w = width / capture.width; 
  let h = height / capture.height;

  // load the pixels of the webcam capture
  capture.loadPixels();
  
  // loops to iterate over each pixel of the webcam capture
  for (let j = 0; j < capture.height; j++) {
    for (let i = 0; i < capture.width; i++) {
      // calculate the pixel index
      const pixelIndex = (i + j * capture.width) * 4;
      // rgb component of the pixel
      const r = capture.pixels[pixelIndex + 0];
      const g = capture.pixels[pixelIndex + 1];
      const b = capture.pixels[pixelIndex + 2];
      
      // calculating brightness 
      const brightness = (r + g + b) / 3;
      
      // Adjust the fill color based on brightness
      // map brightness to green color range
      let fillColor = map(brightness, 225, 0, 225, -150);
      fill(0, fillColor, 0);
      
      textSize(w * 1.3);
      textAlign(CENTER, CENTER);
      
      // retrieve a character from the matrixText string based on charIndex
      let textChar = matrixText.join('').charAt(charIndex % matrixText.join('').length);
      if (random(1) < 0.05) {
        textChar = matrixText.join('').charAt(floor(random(matrixText.join('').length)));
      }
      text(textChar, i * w + w / 2, j * h + h / 2);
      charIndex += rainSpeed;
    }
  }
}

 

I still could not replicate the rain sequence in the visuals as expected. I hope to improve that in the future.

 

Week 3 Reading Response

I enjoyed “The Art of Interactive Design.” While doing the coding assignment for this week, I was questioning myself: What is interactivity, and why is it significant in art? This week’s reading perfectly aligned with that specific question. I liked how Crawford emphasized that interactivity is a form of art. And this also sheds light on understanding audiences’ needs and desires while creating any interactive experience, just as with any other form of art.  Although I had implemented a basic form of interaction in the last two weeks’ coding problem, I had not actually thought of it as an artistic expression and also did not consider my responsibility to present a coherent interactive experience. After reading Crawford’s thoughts, I went back and reevaluated this week’s coding assignment and tried to think about the audience’s perspectives. Crawford’s perspective on bringing objectivity into the subjective world of interactions was really thought-provoking. I hope to incorporate the three dimensions of interaction labelled in the reading into my future projects and optimize designs for all three dimensions. However, I am still a bit confused between the lines of low-level interaction and no interactivity.

Assignment 3: OOP

The task for this assignment was to implement an interactive visual experience using the concepts we learned last week, primarily object-oriented programming and arrays. For this assignment, I wanted to play with the visual perceptions of the audience. I wanted to create a very simple piece, but I also wanted to make it look like the piece has different underlying layers by incorporating a sense of depth. To give the illusion of 3D, I modified how shapes are displayed to include shading and perspective distortion. I introduced a z-axis movement illusion for the shapes. In the draw function, an interactive background changes hue based on mouse position, and shapes are updated and displayed with 3D effects.

class DynamicShape {
  constructor(x, y, size) {
    this.x = x;
    this.y = y;
    this.z = random(-20, 20); // Simulate depth
    this.size = size;
    this.rotationSpeed = random(-0.05, 0.05);
    this.color = color(random(360), 80, 80);
    this.type = random(['square', 'circle', 'triangle']);
  }

  // updating the depth
  update() {
    this.z += random(-1, 1);
    this.adjustSizeBasedOnDepth();
  }

  // displaying different shapes
  display() {
    push();
    translate(this.x, this.y);
    scale(map(this.z, -20, 20, 0.8, 1.2)); 
    rotate(frameCount * this.rotationSpeed);
    fill(this.color);
    noStroke();
    switch (this.type) {
      case 'square':
        rectMode(CENTER);
        rect(0, 0, this.size, this.size);
        break;
      case 'circle':
        ellipseMode(CENTER);
        ellipse(0, 0, this.size, this.size);
        break;
      case 'triangle':
        this.drawTriangle();
        break;
    }
    pop();
  }

  // adjusting the depth
  adjustSizeBasedOnDepth() {
    this.size = map(this.z, -20, 20, 10, 50);
  }

  // drawing the triangle
  drawTriangle() {
    triangle(
      this.size * cos(0), this.size * sin(0),
      this.size * cos(TWO_PI / 3), this.size * sin(TWO_PI / 3),
      this.size * cos(TWO_PI * 2 / 3), this.size * sin(TWO_PI * 2 / 3)
    );
  }
}


// draw fucntion
function draw() {
  drawInteractiveBackground();
  shapes.forEach(shape => {
    shape.update();
    shape.display();
  });
}

 


The most challenging part was for me to figure out the static floral background and how to keep changing the hues to keep track of the depth of the newly created objects. Initially  struggled a bit with the new z-axis movement.

Reading Reflection – Week 2

I enjoyed Casey Reas’s talk more than I expected I would. Although the presentation is from 2012, I think the talk is more important than ever in the new rise of Generative AI arts. In his talk, he explained his work, which utilizes chance operations and algorithms to create visual outcomes, and claimed these demonstrate the tension between order and chaos. However, the more I looked at his works, the more I felt the presence of an emerging order or pattern out of the caos. I think this just further exemplifies the quintessential human endeavor to find coherence in chaos. The technical difficulties of achieving randomness and the limitations of noise in the arts have been explored quite well in the talk, raising the question of whether it is truly possible to generate randomness with technology where everything is bound by the code. Reas’s assertion, “Artists maintain order in the face of nature,” stuck with me throughout the talk. And this assertion again questions the ownership of the generative AI arts: is the AI or the person behind the model trying to maintain that order?

Assignment 2: Loops

For this assignment, we were asked to use Loops to create a simple work of art. A few years ago, I came across one video of Lex Fridman on my YouTube recommendation on “Donut-shaped C code that generates a 3D spinning donut” . It is a video featuring Andy Sloane’s work on the 3D Spinning donut. Since then, this video has been on my watch later playlist. The blog post from Andy Sloane himself on this work can be found here. I always wanted to recreate this. On my first reading of the blog post, I understood the math easily, but back then, I did not have the courage to take on the coding portion.

After going through the listed examples for this assignment, it was clear to me that Andy’s work is also a work of art. So, I quickly decided to recreate that work in p5.js. After looking into the source code, I quickly realized the fearsome code is nothing but a combination of lots of loops.

In this artwork, the 3D effects are created using the mathematical process called z-buffering. This involves creating a 2D array called a z-buffer, which stores the distance to the nearest object for each pixel on the screen. When the code renders an object, it calculates the distance to the object for each pixel and compares it to the value stored in the z-buffer for that pixel. If the object is closer than the object currently stored in the z-buffer, the code updates the z-buffer with the new distance and draws the object on the screen. This process is repeated for each object in the scene, and the result is a 2D image that appears to be 3D. The detailed mathematical explanation can be found on Sloane’s blog here.

I could not directly use the source code provided by Andy Sloane. His art work was based on C code, which did not have functions like draw() in p5.js. So, although I took the mathematical grounding from his shared source code, I had to rewrite the algorithm to make this work in p5.js.

// Function to draw the rotating donut
function canvasframe() {
  // Increment angles for rotation effect
  A += 0.07;
  B += 0.03;

  // Calculating the cosines and sines of A, B
  let cA = cos(A), sA = sin(A),
      cB = cos(B), sB = sin(B);
  
  // parameters to scale and move the donut
  let scale = 400; // variable to scale up or down the donut
  let xOffset = width / 2;
  let yOffset = height / 2;
  let K2 = 10; // perspective adjust
  
  // Draw the donut
  for (let j = 0; j < TWO_PI; j += 0.3) {
    let ct = cos(j), st = sin(j);
    for (let i = 0; i < TWO_PI; i += 0.1) {
      let sp = sin(i), cp = cos(i);
      let ox = 2 + 1 * ct, 
          oy = 1 * st;

      // transform the 3D points to 2D using rotation and perspective
      let x = ox * (cB * cp + sA * sB * sp) - oy * cA * sB;
      let y = ox * (sB * cp - sA * cB * sp) + oy * cA * cB;
      let ooz = 1 / (K2 + cA * ox * sp + sA * oy); // One over Z (Depth)
      let xp = xOffset + scale * ooz * x; // translate and scale to fit the canvas
      let yp = yOffset - scale * ooz * y; // translate and scale to fit the canvas
      let L = 0.7 * (cp * ct * sB - cA * ct * sp - sA * st + cB * (cA * st - ct * sA * sp)); // calculate brightness
      if (L > 0) {
        fill(255 * L); // Set the fill color based on the brightness
        rect(xp, yp, 2, 2); //draw the point with a slightly larger size for better visibility
      }
    }
  }
}

I successfully recreated the donut’s 3D effect. However, I could not finish up the ASCII donut. So, in future I would love to finish up the work and replicate the entire art work.

Assignment 1: Self Portrait | Hasibur

For this assignment, we were asked to create a self-portrait using p5.js. I started by looking into the listed self-portraits and previous students work. I was heavily inspired by a few of them and later incorporated a few styles from their portraits. [1. https://editor.p5js.org/Sarthak-Malla/full/3NRqaQVKQ, 2. Koala portrait, 3. Assignment 1: Self-portrait]

I wanted to have a full-body portrait. However, I could not settle on one. Later, I came across this portrait online and decided to follow a similar structure. I initiated the process by sketching a rudimentary outline, focusing initially on the facial features. I used the bezier for the eye brows and the curveVertex for the hair. For the bezier shape, I used this tutorial. In the rest of the portrait, I just used the primitive shapes.

Each cell is a dynamic entity, its behavior governed by its position and the cursor’s proximity. The canvas isn’t just a static backdrop but a living part of the portrait, changing its hues and vibrancy in real-time. Each cell on the canvas pulsates with an oscillating brightness, creating a mesmerizing wave effect. This is not just a pre-programmed animation; the cells also react to the position of your mouse, creating an interactive dance of light and shadow.

function drawBackground() {
  for (let r = 0; r < rows; r++) {
    for (let c = 0; c < columns; c++) {
      let distance = dist(mouseX, mouseY, c * cellWidth, r * cellHeight);
      let offset = map(distance, 0, sqrt(sq(width) + sq(height)), 1, 0);

      let wave = (sin(time - c * r * 0.1) + 1) / 2;
      let brightness = map(wave, 0, 1, 100, 255) * offset;

      cells[r][c] = brightness;

      fill(brightness * 0.9, brightness * 0.7, brightness); 
      noStroke();
      rect(c * cellWidth, r * cellHeight, cellWidth, cellHeight);
    }
  }
}

The eyes follow your cursor, adding a layer of depth and engagement. The mouth opens and closes, reacting to the cursor’s vertical position, making the portrait not just seen but also felt.

function drawMouth() {
  noStroke();

  // calculate the openness of the mouth based on mouseY position
  let deltay = ((400 - mouseY) / 400) * 13 + 6;
 
  if(deltay <= 9.5) deltay=9.5;

  // outer mouth (lips)
  fill(255, 150, 122); // color for lips
  ellipse(200, 173, 27, deltay);

  // inner mouth (white part to represent teeth or inside of mouth)
  let innerMouthHeight = deltay - 2; 
  fill(255); // white color for the inner mouth
  if (innerMouthHeight > 8) {
    // show inner mouth only if it's significantly open
    ellipse(200, 173, 20, innerMouthHeight);

    // black line to split the white part (teeth or mouth separation)
    if (innerMouthHeight > 10) {
      fill(0); // black color for the separation
      let separationHeight = 4; // height of the separation line
      rect(200 - 10, 173 - separationHeight / 2, 20, separationHeight, 10); // centered black line
    }
    else {
      fill(0)
      let separationHeight = 1; // height of the separation line
      rect(200 - 10, 173 - separationHeight / 2, 20, separationHeight, 10); 
    }
  }
}

While working, I couldn’t figure out the nose style. Most portraits that I have seen use triangles or similar shapes. I found this portrait to have a very distinctive style, which I followed. Creating the hair shape took a lot of time for me. I am still not particularly happy with the hair. I wanted more realism for the hair shape.

In the future, I want to create a Lego character resembling my character and try to create a self-portrait based on the Lego character, which I believe would be easily achievable using the primitive shapes.