Assignment 4: Generative Text

Concept & Inspiration

Aaron Sherwood’s portfolio website had me impressed since the past 2 years, and it had been on my to-do list of things to learn just because of how addictive its animations were. Therefore, I decided to pursue this assignment in p5js to produce something similar and learn something new.

The website I took inspiration from is linked here

Embedded Canvas

The assignment outcome is attached as an embedded sketch as follows:

Code

The methodology to convert the text to points:

function setup() {
  createCanvas(600, 400);
  textSize(64);
  textFont(font);

  for (let i = 0; i < texts.length; i++) {
    let points = font.textToPoints(texts[i], 50 + i * 150, height / 2,50, {sampleFactor: 0.75, simplifyThreshold: 0 });
    for (let j = 0; j < points.length; j++) {
      let p = new Particle(points[j].x, points[j].y);
      particles.push(p);
    }
  }
}

Object Oriented Nature of the Particles!

class Particle {
  constructor(x, y) {
    this.pos = createVector(x, y);
    this.vel = createVector(random(-1, 1), random(-1, 1));
    this.acc = createVector(0, 0);
    this.target = createVector(x, y);
    this.size = 2;
    this.maxSpeed = 2;
    this.maxForce = 0.1;
    this.attractRadius = 50;
    this.attractForce = 0.1;
    this.color = color(255, 255, 255);

  }

  behaviors() {
    let arrive = this.arrive(this.target);
    this.applyForce(arrive);
    this.repel();
  }

  repel() {
    let mouse = createVector(mouseX, mouseY);
    let distance = p5.Vector.dist(this.pos, mouse);
    if (distance < this.attractRadius) {
      let repelForce = p5.Vector.sub(this.pos, mouse).normalize().mult(this.attractForce);
      this.applyForce(repelForce);
    }
  }
    attract() {
    let mouse = createVector(mouseX, mouseY);
    let distance = p5.Vector.dist(this.pos, mouse);
    if (distance < this.attractRadius) {
      let attractForce = p5.Vector.sub(mouse, this.pos).normalize().mult(this.attractForce);
      this.applyForce(attractForce);
    }
  }

  applyForce(f) {
    this.acc.add(f);
  }

  arrive(target) {
    let desired = p5.Vector.sub(target, this.pos);
    let d = desired.mag();
    let speed = this.maxSpeed;
    if (d < 100) {
      speed = map(d, 0, 100, 0, this.maxSpeed);
    }
    desired.setMag(speed);
    let steer = p5.Vector.sub(desired, this.vel);
    steer.limit(this.maxForce);
    return steer;
  }

  update() {
    this.pos.add(this.vel);
    this.vel.add(this.acc);
    this.acc.mult(0);
  }

  show() {
    // Set the fill color based on the velocity of the particle
    let vel = this.vel.mag();
    if (vel < 0.5) {
      this.color = color(255, 0, 0); // red
    } else if (vel < 1) {
      this.color = color(255, 255, 0); // yellow
    } else {
      this.color = color(255, 255, 255); // white
    }

    fill(this.color);
    noStroke();
    ellipse(this.pos.x, this.pos.y, this.size, this.size);
  }
}

 

Problems

The primary issue I faced was to discover the font.textToPoints() function and its complex usage. The other difficult bits was to repel or attract the points and adjust the speed and colors of it!

Assignment 4 – Generative Text

Concept

For this project, I decided to go for the Generative Text option, because I felt that way I could get some experience both with manipulating text and implementing csv files as well. I really wanted to use particle systems to generate text and do some fun stuff with them so I went ahead and explored a little on how to implement a particle system in p5.js and use it to generate text.

Implementation

I started off by making a Particle class. This class essentially contains all the attributes that are related to the particles that are generated. For instance, it contains their velocity, acceleration, maximum speed, and whether they have left the canvas yet or not, or in other words, if they are alive. I thought it would be a great idea to use vectors for this scenario since velocity and acceleration basically are defined by a magnitude and direction (which in essence is a vector). So I went ahead and made unit vectors for both of them.

// creating the class for the particles
class Particle {
  // the x and y positions where the particle would appear
  constructor(x, y) {
    this.pos = createVector(x, y);
    this.vel = createVector(random(-1, 1), random(-1, 1)); // to include all directions (360 deg)
    this.acc = createVector(random(-1, 1), random(-1, 1)); // to include all directions (360 deg)
    this.maxSpeed = 20; // limiting max speed of the particle
    this.prevPos = this.pos.copy(); // will use this as a starting point to create a line 
    this.alive = true; // to check if the particle has left the canvas
  }

As for the methods, the update method would add acceleration to the velocity vector and increase the acceleration at the same time as well. The particles are displayed as lines by using the current and previous positions of the particles in every frame. This really makes the particles look as though they are flying off like shooting stars when the ENTER key is pressed.

// to make the particle move
update() {
  this.vel.add(this.acc); // to make the particles move with increasing speed
  this.vel.limit(this.maxSpeed); // limiting them to a max speed
  this.pos.add(this.vel); // making them move by changing their position
  this.acc.mult(2); // increasing acc on every update so the lines become longer
}

// making lines to make particles appear
show() {
  stroke(random(255), random(255), random(255));
  // using previous pos and updated pos to make a line for the travelling particle
  line(this.pos.x, this.pos.y, this.prevPos.x, this.prevPos.y);
  // updating the prev position
  this.prevPos = this.pos.copy();
}

Another interesting method I used was the one that allowed the pixels to wrap around the text. I did this by looping over all the pixels in the canvas and checking if the pixel was white or 255. If it was then I would place a particle at that pixel. I didn’t display the text itself, it only appeared for a split second in one of the frames of the draw() function and during that time the particles would stick to that text. The nested for loops I used were as follows:

// the nested for loop traverses over the whole canvas and finds where the 
// white pixels of the text are located
for (let x = 0; x < width; x += 2) { // number after += indicates density of particles
  for (let y = 0; y < height; y += 2) { // lesser number = more performance problems
    let index = (x + y * width) * 4;
    if (pixels[index] == 255) { // checking if the text is present at the current pixel
      let p = new Particle(x, y); // if yes then make a particle at that position
      particles.push(p); // and add it to the array
    }
  }
}

Reflection

I really enjoyed working with text and particles during this assignment and I feel I learned a lot about particle movement, vectors, and pixels; all of which were new concepts for me. I did notice a strange bug however while I was working on this assignment and I wasn’t quite able to fix it. I was initially working on my monitor for this assignment and it was working just fine, but as soon as I opened the p5.js file on my laptop screen, the particles that made up the word were not aligned at their correct positions on the screen. I really tried to dig deep into this issue but I couldn’t understand why it was happening. Due to this, I had to fix the position of the random particle text on the center of the screen instead of using the mousePos  to generate it. I would love to explore more on how I could resolve this bug as I have never encountered a bug like this before in p5.

Week 4 – Data Visualization

Concept

Having taken MVC last semester, as with many others, spherical coordinates are still stuck in my head. Since my last week’s assignment was space themed, I wished to stick with it for a bit longer. With the inspiration from this p5js sketch and the help of this video, I was able to understand how to plot points using the three axes in p5js. As there are few things to be plotted on the globe compared to the standard map, I decided to concern this project with meteorite landings.

I downloaded my CSV file from the NASA website and cleaned it up in Microsoft Excel such that it contained only the relevant rows, mass, latitude, and longitude. Furthermore, I removed the rows with blank cells so that they do not take up more space than needed.

function preload() {
  meteors = loadTable("M_New.csv", "csv");
}

After plotting the points I was left with the following sketch:

https://editor.p5js.org/ajlasacic/full/yxr9xvKC4

Note: The program is very demanding so I posted only the link in order not to crash the website. By changing the step variable, more landings can be seen.

Challenges

The most challenging part was understanding where the latitude and longitude are supposed to be pasted. Since they were given in degrees in the CSV file, I put the angleMode() to DEGREES. Unfortunately, this plotted the points only on one side of the globe, which took some time to understand why. After reverting back to normal parameters, the points were plotted just right.

let mass = meteors.getColumn(0);
  let lat = meteors.getColumn(1);
  let lng = meteors.getColumn(2);

  for (let i = 2; i < n; i += step) {
    let lat_ = lat[i];
    let lng_ = lng[i];

    strokeWeight(map(mass[i], 0, upper_mass_size, 0.7, 5));
    if (flag == true) {
      stroke(map(mass[i], 0, upper_mass_color, 120, 30), 100, 90);
    } else {
      stroke(255);
    }

    let z = r * cos(lat_);
    let y = r * sin(lat_) * sin(lng_);
    let x = r * sin(lat_) * cos(lng_);
    point(x, y, z);
  }

Another challenge was understanding how to map the size of the magnitude of the meteors on the map. By using the MIN and MAX functions in Excel, I extracted the smallest and biggest values within the dataset and set them as the values of their respective variables. By using the map() function (considerably the best function in p5js), I was able to set different sizes to the points within the sketch. By trial and error method, I was able to conclude 0.7 and 5 were perfect

strokeWeight(map(mass[i], 0, upper_mass_size, 0.7, 5));

Although the meteors in the dataset are quite similar in mass, slight differences can be seen on the graph. Lastly, I wished to create the sphere more interactive by changing colors. The colors were also added using the map() function, but I was stuck on how to change them when the mouse was pressed. By adding a global boolean flag variable, I was able to overcome this issue and change the color by setting its value to true or false.

Reflection

Although I am not quite satisfied with this project, I am grateful that I could achieve its main purpose which was plotting in 3d. I tried adding texture to the sphere to show the countries, but the picture did properly wrap no matter how much I edited it. In the future, I wish to make the plot more readable by adding either country outlines or an earth texture. Overall, I am pleased that I know how to plot in 3d using p5js.

 

Generative Text_Aaron

Concept

The idea for this code was to generate a random text/ character on the canvas using noise feature. the canvas color becomes darker and darker with each word generated and printed on the screen. I was inspired by the idea of dots disappearing from my computer screen as I worked on a data visualization project I had given up on.

Implementation

The concept was implemented using p5.js. Key technical implementations includes preload and noise feature.

Challenges

Some of the challenges I faced was randomizing the position of both the word and character on the screen; I couldn’t solve it.

Generative Text – Aigerim

Inspiration/Concept

For this project, I created a fortune cookie application that gives a random fortune. I love getting fortune cookies and seeing my luck (even though I do not believe in them, it is still a fun activity), and I wanted to transfer that fun little moment into a web application. To create the effect of anticipation one gets when opening the cookie’s package I made a fortune come up as in a typewriter, letter by letter without revealing everything all at once.

The Product

I am really happy with the way it turned out, especially given that I was not really sure what I was going for when I just started the project. The product is really simple and minimalistic, and I love it this way because I want the focus to be on the fortune itself, which I think was achieved pretty nicely.

Challenges

The biggest challenge for me was restarting the typewriter when a user clicks on the mouse and the typewriter is done writing. Since my function is not blocking, I had multiple fortunes written on top of one another at some point. To solve this, I added a boolean printing that is set to True whenever I am about to enter the typeWriter function within start and set it back to False when I am about to leave it. Then, whenever a mouse is clicked, I first check whether the variable is true or not and print a new fortune when the previous one is done printing (or displaying) and the user clicks.

function start() {
  if (printing == false) {
    printing = true;
    typeWriter(random(fortunes), 0, 50, height / 2, 80);
  }
}

function mouseClicked() {
  start();
}

 

HW4: Generative Text Video

Inspiration

The inspiration for this generative text art was to use text to render a live video. The goal was to use the brightness of each pixel in a video capture to determine the color of a letter displayed in the center of the pixel and to keep rendering this video live. The code was inspired from a coding challenge by coding train.

Concept

The concept was to create a generative piece of art that was unique to the video input. The text displayed would change based on the brightness of each pixel, resulting in a dynamic and ever-changing piece of art. The text used was limited to the string “ME” and the letters were displayed in the center of each pixel. The string can also be changed to say something else.

Implementation

The implementation involved using the p5.js library to capture video input and display text on a canvas. The code loops through each pixel in the video capture, calculates the brightness of the pixel in grayscale, and uses that value to set the color of the displayed text. The text is displayed in the center of each pixel and changes based on the brightness of the pixel.

Challenges

One of the main challenges that I faced was to decide on an appropriate dimensions of the rendered video in order for p5.js to not break while looping through the video.

Reflections

Overall, this project was a successful exercise in creating generative art using code. The dynamic and ever-changing nature of the art is an interesting concept that can be expanded upon in future projects. The ability to use video input as the source for the art adds an extra level of interactivity and uniqueness to the piece.

Sketch Link: https://editor.p5js.org/swostikpati/full/U-0U4yVHN

References

A game of Madlibs – Hana

I decided to use the word randomizer task as a way to make a fun game of madlibs.

I loaded all words from a .csv file, and categorized them appropriately. Then I printed the text onto the canvas, leaving spaces for the words which will be chosen by the program and distinguishing them with a red font.

Here is an example of how the specific words are chosen randomly:

//function which randomly chooses and returns a verb through the use of random integers
function chooseVerb() {
  let num = int(random(verbs.length));
  return verbs[num];
}

This same function is repeated for nouns and adjectives as well. This function itself returns the word and I use the returned value to print onto the canvas:

//putting the paragraph together and printing it on the canvas
function printMadLibs() {
  text("Summer is all about the beach! During the daytime, you can ", 10, 20);
  fill("red");
  text(chooseVerb(), 335, 20);
  fill("black");
  text("in the lake or sea and collect seashells and ", 10, 40);
  fill("red");
  text(chooseNoun(), 245, 40);
.
.
.

This was a very fun program to work on since it is based on the popular game of Madlibs. Each time you refresh it will have a new set of words chosen randomly, and sometimes they are very funny, other times they make no sense.

This was the first time I have done any sort of file manipulation in JavaScript, so I am happy to have learned how to do it. I mainly used the class notes and P5 reference page to figure things out.

Assignment 4: Music Visualization

This code uses a circular pattern to represent sound. It plays a song using the p5 library and then plots the song’s amplitude in a circular pattern. The draw method in the code first sets up the canvas and the Amplitude object before preloading the sound file and drawing the visualization.

The circular pattern is produced by the code using the p5.js methods translate, beginShape, vertex, and endShape. The map and random functions are also used by the code to produce the visualization’s design. The amplitude data is likewise stored in an array. The circular pattern is then produced using the amplitude data.

It took some time to get the code to function correctly. I had to confirm that both the sound playback and the amplitude data storage were accurate. I wanted to confirm that the visualization was being rendered properly as well. The toughest issue was appropriately storing the amplitude data in the array.

I was able to add certain design components, such as the random fill color, and the way the sounds circle around the mouse after the code was functioning well. This gives the visualization a beautiful visual component.

I want to enhance the visualization in the future by including more design components. Also, I’d want to provide a mechanism for regulating the visualization’s pace. The user would be able to decide how quickly or slowly the visualization changes thanks to this. The visualization’s size and form may now be adjusted, I’d like to mention. The user would then be able to tailor the display to their preferences.

Homework 04 – Data visualization – “Notable people”

Sketch

Concept

I wanted to do a data visualisation that would show the world in an interesting way. Since I’ve been working on a project which created a dataset of two million notable people in history, I decided to use that dataset. Since this dataset is huge (the whole .csv file is 1 GB), I decided to filter it so that I only look at people in Europe with a notability index as defined in the paper of 23.5 or more. This led to a dataset with 52000 observations and a file size of 4.3 MB.

Each observation (row) in the dataset, after filtering and selecting only the columns I wanted to use, has the birth year, birth location and death location (latitude and longitude) of a notable person in history, as defined by the paper. Therefore I decided to make my visualisation a timeline, from 3500 BC to 2020 AD, where for every year I draw the birth and death locations of all people born at that year. Moreover, the more notable the person as defined by their notability index, the bigger their circle would be. I draw the people as translucent circles, so that if many people were born near the same location, the colors will add up. I picked the color green to represent a birth location, and red to represent a death location.

Code highlight

I’m particularly proud of the part of my code that converts a person’s latitude and longitude into (x, y) coordinates which I use to draw the person later. Latitude and longitude are coordinates on a sphere, while x and y are coordinates on a two-dimensional plane. I essentially needed to figure out a map projection. There are many of these, such as the Mercator projection, Robinson projection, etc. However, I managed to find a projection for which the code is incredibly simple: the Equirectangular projection. This formula is very simple: we map latitude to x and longitude to y. However, this produces a map rotated 90 degrees clockwise, so we also rotate it by using a bit of linear algebra.

function getCoordsFromLatLon(lat, lon) {
  // Equi-rectangular projection.
  // Formula from:
  // https://www.marksmath.org/classes/common/MapProjection.pdf
  // (lat, lon) -> (x, y)
  // But we also rotate 90 degrees:
  // (x, y) -> (y, -x)
  return {x: lat, y: -lon};
}

Note that changing this mapping function will result in different interesting visualizations.

Reflection

The final visualization looks good, especially after tuning a few of the parameters, such as the colors, radii, and transparency of the circles. The dataset I used is huge and has many parameters besides the ones I used. For example, instead of coloring circles based on the person’s birth and death, we can instead use data such as a person’s occupation (discovery/science, culture, leadership, sports/games, other), which might show interesting effects over different locations. Finally, we can can even create a 3D visualization where we place the points on an actual sphere that we might move around. However, the slow performance of p5.js might make this difficult with more than a few thousand data points.

Assignment 3 – Generative Art

Concept

Last semester, I was doing my study away in New York and when the semester ended, I decided to travel to the other end of the country to San Francisco, because why not. While I was there, one of the many places on my checklist was the Golden Gate Park. Inside it was a Japanese Tea Garden. It was one of the most aesthetically pleasing and peaceful places I have ever been. One of the many things I really liked about that tea garden were the unique Japanese trees that it had, and so, as a small tribute of remembrance, I tried to make a Japanese tree of my own for this project.

Implementation

I started off by making the basic shapes for the background. I placed the rectangles for the window, pot, sky, wall and circles for the moon. The main part however, were the leaves. To keep it simple, I made a very basic Class for a circular leaf and then went on to add the functions for its movement and appearance. the idea I had was to fill up different clusters of leaves for the tree. I made three clusters or regions of leaves, each having their own set of leaves. The leaves would fall from the clusters in a random fashion and subsequently be deleted from the array when they exit the canvas from the bottom. I also decided it was a good idea to make use of noise for the first time to make the fall seem more natural. So I went on and added noise in the x-direction while making the leave travel downwards simultaneously.

However straightforward this may seem, I encountered quite a few bugs along the way and thankfully managed to resolve them. I will share the significant ones here.

Positioning the Leaves

I started off by creating the x0 and y0 coordinates for the center of the cluster where the leaf object would be. I also specified the clusterWidth and clusterHeight in the leaf’s constructor. The initial position of the leaf was placed randomly in the cluster by selecting a random position in the rectangular area bounded by it:

class Leaf {
  // x0 and y0 specify the center of the cluster of leaves (one of the 3 clusters)
  constructor(x0, y0, clusterWidth, clusterHeight) {
...

Following this, I found out that I had to use a separate variable to store this initial x-position since I was making use of noise in the move() function of the leaf’s class. I couldn’t add the xPos variable to itself to update its value since that would enlarge it too abruptly inside the draw function thanks to the noise() function. It took me quite some while to figure this out as this wasn’t a very straightforward bug, but I eventually got there:

// using a separate variable for the initial position of x since xPos will
    // always be changing in draw() and this will cause abrupt behavior in the
    // leaf's x-movement if used along with noise inside move()

    // positioning initialX, xPos, and yPos within the cluster
    this.initialX = random(x0 - clusterWidth, x0 + clusterWidth);
    this.xPos = random(x0 - clusterWidth, x0 + clusterWidth);
    this.yPos = random(y0 - clusterHeight, y0 + clusterHeight);

...

// for the leaf's falling movement
  move() {
    // only make the leaf fall if the fall attribute of the object is true
    if (this.fall) {
      // giving some noise in the x direction to give a natural feel while falling
      this.xPos = this.initialX + noise(this.xoff) * 100;
      this.xoff += this.xincrement;
      // incrementing y to make the leaf fall vertically
      this.yPos += this.ySpeed;
    }
Removing the Leaves

Since I was keeping a check both on whether a leaf is alive or not and whether it is falling or not, I had to implement both of these concurrently. For instance, if a leaf reached the end of the canvas and it got deleted from the array, the code that randomly chooses a leaf to fall would not know whether the leaf is alive or not and could encounter an empty object. So I added a check for whether the leaf is alive before making it fall and it fixed the bug that I was facing. Moreover, if the leaves[] array becomes empty, the random(0, leaves.length) would return zero and that would cause an error again since the element at index zero doesn’t exist. So I added a check for that as well:

// choosing a random leaf to fall every 20 frames

  // if the leaves[] array becomes empty, it causes an error because the length is 0 but
  // the 0th element doesn't exist anymore so we provide a check for that as well

  if (frameCount % 20 == 0 && leaves.length != 0) {
    // choosing a random leaf index to make it fall from the tree
    index = int(random(0, leaves.length));

    // check if the leaf at the random index is alive and present inside the array
    if (leaves[index].alive) {
      // if yes then make the leaf fall
      leaves[index].fall = true;
    }
  }
Randomizing Leaf Color

At first I tried to do this within the Leaf class itself but then I realized that if I choose the color inside the draw() function of the Leaf class, it would cause the leaf to change color continuously because the draw() function loops continuously. I couldn’t place the array inside the standard draw() function either for the same reason. So I came up with the idea of creating a global array that would store the two leaf colors (shades of purple) and then choose the color inside the attributes of the leaf class as a this.color attribute:

// array which stores the two shades of purple for the leaves
let leafColors = ["#963990", "#4B2454"];

class Leaf {
  // x0 and y0 specify the center of the cluster of leaves (one of the 3 clusters)
  constructor(x0, y0, clusterWidth, clusterHeight) {

...

    // choosing a random shade of purple from the two specified in the global array
    this.color = leafColors[int(random(0, 2))];
  }

...

// drawing the leaf
  draw() {
    noStroke();
    fill(this.color);
    ellipse(this.xPos, this.yPos, this.width, this.height);
  }

Reflection

I feel like I managed to get the kind of aesthetic feel I was going for in this assignment. The color combinations worked really well and the tree stood out within a minimalistic background without being too overwhelming. I went for a kind of peaceful and calm feel and the falling leaves did well to prortray that feeling as well. It was unfortunate that I had to start from scratch after losing progress mid-assignment but gladly I was able to recall the functions I made and how I implemented them. It was disheartening at first but after putting in some extra hours I was able to remake the project exactly how I wanted to in the first place. As for the future, I would definitely like to save my progress often and make the habit of hitting the save shortcut from time-to-time, although it sounds trivial but its something i would definitely like to work on. Excited to see what I can do with transformations for our next assignment!