Assignment 2: Looping

For the second assignment, I took inspiration from Random Squares by Bill Solomyjec.

I liked the idea of incorporating randomness in a controlled manner and how each square was unique.

Implementation

I figured that I needed to first created a grid within my canvas. I was able to do this by using variables and loops to divide the canvas evenly.

let square_length = 80;
let num_rows = 5;
let num_cols = 8
createCanvas(square_length*num_cols, square_length*num_rows);
for(let row = 0; row <num_rows;row+=1){
    for(let col = 0; col<num_cols;col+=1){
      let startx = col*square_length;
      let starty = row*square_length;
      fill(255)
      square(startx,starty,square_length)
    }
}

Then I figured for each square within the grid, I needed a number of random values: an offset for the initial square, a random ratio in the x and y direction for the squares to grow, and a random gap between each square to multiply the ratios by.

I implemented them via multiple 2D arrays in the following way:

for(let row = 0; row <num_rows;row+=1){
    currrow = []
    for(let col = 0; col<num_cols;col+=1){
      currrow.push([random(minoffset,square_length-base_length-1),random(minoffset,square_length-base_length-1)])
    }
    offsetlist.push(currrow);
  };
  for(let row = 0; row <num_rows;row+=1){
    currrow = []
    for(let col = 0; col<num_cols;col+=1){
      currrow.push([random(maxratio),random(maxratio)]);
    }
    ratiolist.push(currrow);
  };
  for(let row = 0; row <num_rows;row+=1){
    currrow = []
    for(let col = 0; col<num_cols;col+=1){
      currrow.push(random(mingap,maxgap));
    }
    gaplist.push(currrow);
  };

Then, using these values, I could draw each square in each grid location by extracting these random values as I drew each grid square.

for(let row = 0; row <num_rows;row+=1){
  for(let col = 0; col<num_cols;col+=1){let [xoffset, yoffset] = offsetlist[row][col]
      let [xratio, yratio] = ratiolist[row][col]
      let squaregap = gaplist[row][col]
      for(let sqoffset=0; sqoffset<min(xoffset/xratio,yoffset/yratio); sqoffset+=squaregap){
        let offsetedx = startx+xoffset-sqoffset*xratio;
        let offsetedy = starty+yoffset-sqoffset*yratio;
        let offsetedlength = base_length+sqoffset*(1+max(xratio,yratio))
        square(offsetedx,offsetedy,offsetedlength);
      }
  }
}

The min(xoffset/xratio,yoffset/yratio) in the innermost for loop controls the boundaries of the largest square, ensuring that the squares do not extend beyond the top or left of the grid. This prevents a lot of the potential chaos.

Nicholas Week 1 Self-Portrait

Inspiration

I initially had no idea how I wanted my portrait to look, but I knew I wanted to do something a bit conceptually out there. Probably something within the lines of weird colors and shapes that don’t make sense.

The Process

I decided I wanted a background that cycled through colors and was dynamic. I looked at the color picker on google and moved the color slider bar around and saw that the HSV method of describing color would be easiest to represent as a rainbow-like cycle in HSV was to go from (0°,x%,y%) to (360°,x%,y%) and back down. I found a converter for HSV to RGB and wrote a function that translated HSV to RGB from this page in p5.js and had a variable increase that I used as a parameter.

function hsv_to_rgb(h, s, v) {
  h = h % 360;
  let c = v * s;
  let x = c * (1 - abs(((h / 60) % 2) - 1));
  let m = v - c;
  let rp, gp, bp;
  //r,g,b prime depend on value of h in degrees
  if (0 <= h && h < 60) {
    [rp, gp, bp] = [c, x, 0];
  } else if (60 <= h && h < 120) {
    [rp, gp, bp] = [x, c, 0];
  } else if (120 <= h && h < 180) {
    [rp, gp, bp] = [0, c, x];
  } else if (180 <= h && h < 240) {
    [rp, gp, bp] = [0, x, c];
  } else if (240 <= h && h < 300) {
    [rp, gp, bp] = [x, 0, c];
  } else{
    [rp, gp, bp] = [c, 0, x];
  }
  //return r g b values
  return [(rp + m) * 255, (bp + m) * 255, (gp + m) * 255];
}

I then decided to make a function to draw a shirt, using the cycle but switching the b and g values so that a different cycle is used. Using variables, I centered the shirt and added a small triangle for the neck.

let shirtheight = 180;
let shirtwidth = 250;
let triangleoffset = 20;
let triangleheight = 20;
function drawshirt(r,b,g){
  //drawing a shirt with random colors
  fill(r,b,g)
  stroke(200)
  rect((width-shirtwidth)/2, height-shirtheight, shirtwidth, shirtheight, 60, 60, 0, 0);
  //draw a triangle over the shirt 
  fill(255)
  triangle(width/2+triangleoffset,height-shirtheight,width/2-triangleoffset,height-shirtheight,width/2,height-shirtheight+triangleheight) 
}

Then I looked to add a head with some dynamic parts: a moving head, pupils, mouth, and an offset so I can have multiple heads. All these parts were made with variables for height and width so that all objects function with varying canvas sizes. the use of the min and max functions are for creating bounds for the movement of body parts. I decided to use a sine function with bounds that moved with the shade of the background to have some type of synchronization with the background, but it’s difficult to see it on the canvas. The mouth is bound rectangularly, and the user can move their cursor to change the shape.

function drawface(offsetx, offsety){
  fill(255)
  //lets draw a head
  rect((width-headwidth)/2+offsetx, height-shirtheight-headheight+offsety, headwidth, headheight, 60, 60, 60, 60);
  
  fill(255)
  //add some eyes
  rect((width-headwidth)/2+eyeoffset+offsetx, height-shirtheight-headheight+eyeheightoffset+offsety, leyewidth, leyeheight, 60, 60, 60, 60);
  
  rect(width-(width-headwidth)/2-eyeoffset-reyewidth+offsetx, height-shirtheight-headheight+eyeheightoffset+offsety, reyewidth, reyeheight, 60, 60, 60, 60);
  
  //add some pupils that move, bounded by the height of the eyes
  fill(0)
  rect(width-(width-headwidth)/2-eyeoffset-reyewidth+offsetx, min(max(height-shirtheight-headheight+eyeheightoffset,height*sin(currbg/lpupiledelay)),height-shirtheight-headheight+eyeheightoffset+reyeheight-pupilheight)+offsety, reyewidth, pupilheight, 60, 60, 60, 60);
  rect((width-headwidth)/2+eyeoffset+offsetx, min(max(height-shirtheight-headheight+eyeheightoffset,height*sin(currbg/rpupiledelay)),height-shirtheight-headheight+eyeheightoffset+leyeheight-pupilheight)+offsety, leyewidth, pupilheight, 60, 60, 60, 60);
  
  //add a triangular mouth in the center of the face
  triangle(
    min(width/2+mouthoffset+offsetx,max(mouseX,width/2-mouthoffset+offsetx)),
   min(max(height-shirtheight-2*mouthoffset+offsety, mouseY),height-shirtheight+offsety),
   width/2+mouthoffset+offsetx,
   height-shirtheight-mouthoffset+offsety,
   width/2-mouthoffset+offsetx,
   height-shirtheight-mouthoffset+offsety
  )

}

Since all positional values had an offset x and y, I added a for loop in the draw function that added and removed faces at a rate dependent on variable faceincval limited by variable numfaces.

for(let x = 1; x <=numfaces; x+=1){
    drawface(x*10,x*10)
  }
numfaces = maxfaces*sin(faceincval)
faceincval+=0.05

The final product looks as follows:

Conclusion:

I had a lot of fun with the dynamics in this assignment, with all the moving parts.  I think I was able to create a portrait that is unconventional and a bit unnatural but still represents me on a more metaphorical level.

I found that the code began to get more complicated as I added more pieces, with subtractions from subtractions based on many components quickly began to make less and less sense the more I built. Looking forwards, I’d like to write cleaner code that is more legible and makes more sense to another user.