Ellan Suder – Final Project

I wanted to make something visually appealing and interesting to play around with. I first started with the white dot and kept building to it every few days, like the glowing colors, the grid background, and eventually some floating particles.

The spacebar control was actually a mistake I made when I was trying to put in the attraction behavior, but I ended up liking it so I added it to the final program.

Controls: Control the dot with your mouse. Press A to attract, hold dot to repel. Hold spacebar to stop and control particles with mouse. Press X to delete the text.

(Holding the dot while pressing A looks pretty cool!)

sketch

/*
Ellan Suder
15104 1 D
esuder@andrew.cmu.edu
Final Project
*/

// variables for main body
var t = 0;
var r = 0;
var xLoc = 250;
var yLoc = 250;
var diffx = 0;
var diffy = 0;
var targetX = 250;
var targetY = 250;
var diameter = 20;
var opacity = 0;
var dragging = false;

// variables for grid + dots
var freq = 0.001;
var dotColor = 35;

// variables for particles
var particles = [];
var numP = 30;
var particleColor = 255;
var capturedP = 0;
var particleCaptured = false;
var stop = false;
var attract = false;
var diffxP = 0;
var diffyP = 0;

// variable for showing text
var showText = true;
  
function setup() {
    createCanvas(500, 500);
    noCursor();
  
    // make particles
    for (var i = 0; i < numP; i++) {
        var p = makeParticle(random(width), random(height),
                             random(-0.1, 0.1), random(-0.1, 0.1));
        particles.push(p);
    }
}

// drawing the grid, 
// the main white dot, 
// the particles,
// and the instructions text.
function draw() {
    background(0,20);
    noStroke();
    
    // make grid pattern using for loop
    for(var q = 0; q < height/20; q++)
    {
      for(var w = 0; w < width/20; w++)
      {
           // I measured distance from each rect to the center
           // to create subtle gradient. smaller dist = lighter
           dGrid = dist(w*20,q*20,width/2,height/2);
           fill(30 - dGrid/10);
           rectMode(CENTER);
           rect(w*20+10,
                q*20+10,
                15,15);
        
           // creates dot if random number < freq
           // when mouse is pressed, freq increases and
           // chance of making dot increases
           e = random(0,1);
           if(e < freq) {
             fill(dotColor);
             ellipse(w*20+20,
                     q*20+20,
                     5,5);
           }
      }
    }
  
    diffx = mouseX - xLoc;
    diffy = mouseY - yLoc;
    r += 0.1;
    xLoc += 0.05*diffx + sin(r/20)/10;
    yLoc += 0.05*diffy + cos(r/20)/10;
  
    t += 0.01;
    randomColor = color(noise(t)*255,noise(t)*255,noise(t+60)*255, opacity);
    
    // main body (white dot controlled by mouseX and mouseY
    // with some easing for aesthetic purposes)
    fill(255);
    ellipse(xLoc, yLoc, diameter/2, diameter/2);
    // layers of colored circles
    opacity = 10;
    fill(randomColor);
    ellipse(xLoc + sin(r)/2, yLoc + cos(r)/2, diameter*2, diameter*2);
    ellipse(xLoc + sin(r)/2, yLoc + cos(r)/2, diameter*3, diameter*3);
    ellipse(xLoc + sin(r)/2, yLoc + cos(r)/2, diameter*5, diameter*5);
    ellipse(xLoc + sin(r)/2, yLoc + cos(r)/2, diameter*8, diameter*8);

    // when dragging == true, the main dot goes to the mouse
    // and spins faster
    if (dragging) {
        xLoc = mouseX + sin(r*50)*5;
        yLoc = mouseY + cos(r*50)*5;
    }
   
    // draw particles
    for (var i = 0; i < particles.length; i++) {
      var p = particles[i];
      p.step();
      p.draw();
    }
   
    if (showText) {
      let words = 'control the dot with your mouse.       press A to attract, hold dot to repel.  hold spacebar to stop and control particles with mouse.';
      textSize(28);
      strokeWeight(1);
      fill(255,100);
      text(words, width/2, height/2, 
                  width/2+30, height/2+80);
      textSize(15);
      text('press x to remove message.', 2*width/5, 3*height/4);
    }
}

// controlling the behavior of the particles
function particleStep() {
    if (stop) {
        // when space is pressed, particle motion halts
        // and you can control them with the mouse
        this.x += 0.05*diffx;
        this.y += 0.05*diffy;
        this.dx = 0;
        this.dy = 0;
    } else if (attract) {
        // when 'a' or 'A' is pressed, particles are drawn
        // to the mouse
        diffxP = mouseX - this.x;
        diffyP = mouseY - this.y;
        this.x += 0.05*diffxP;
        this.y += 0.05*diffyP;
    } else {
        // if no keys are pressed
        this.x += this.dx;
        this.y += this.dy + cos(r)/5;
    }
    
    // bounce off right
    if (this.x > width) {
        this.x = width - (this.x - width);
        this.dx = -this.dx;
    // bounce off left
    } else if (this.x < 0) {
        this.x = -this.x;
        this.dx = -this.dx;
    }
    // bounce off bottom
      if (this.y > height) {
        this.y = height - (this.y - height);
        this.dy = -this.dy;
    // bounce off top
    } else if (this.y < 0) {
        this.y = -this.y;
        this.dy = -this.dy;
    }
  
    // If mouse gets close to a particle, particle becomes captured
    // and the number of captured particles increases.
    // Size of orbit is fixed, but the modifier of the angle is
    // randomized so that each particle's orbit speed is different.
    if (dist(this.x, this.y, xLoc, yLoc) < 20) {
      this.particleCaptured = true;
      capturedP += 1;
      this.orbit += capturedP;
      this.modifier = random(-1,1);
    }
  
    // This controls what a captured particle does
    // captured particles orbit the mouse.
    // I keep track of number of captured particles
    // because each newly captured particle's orbit
    // increases slightly so they don't overlap.
    if (this.particleCaptured) {
      this.x = xLoc + this.orbit*sin(r*this.modifier);
      this.y = yLoc + this.orbit*cos(r*this.modifier);
    }
   
    // Captured particles get launched outwards when dragging == true
    // Reset orbit of all captured particles
    if (dragging & this.particleCaptured) {
      this.particleCaptured = false;
      this.orbit = 5;
      this.dx = random(-5,5);
      this.dy = random(-5,5);
      this.x += this.dx;
      this.y += this.dy;
    }
}

function particleDraw() {
    stroke(255,150);
    strokeWeight(3);
    point(this.x, this.y);
}

// when mouse is pressed close to the main white dot, 
// freq of grid dots increases, 
// dragging == true,
// and # of captured particles resets
function mousePressed() {
    if (dist(xLoc, yLoc, mouseX, mouseY) < 3*diameter) {
        dragging = true;
        capturedP = 0;
        freq = 0.006;
        dotColor = 60;
    }
}

function mouseReleased() {
    dragging = false;
    freq = 0.001;
    dotColor = 35;
} 

// when keys are pressed, 
// variables become true and the program changes the behavior
// in the particleStep function.
// spacebar -> stop particles and control them
// a or A -> particles attracted to mouse
function keyPressed() {
  if (key == ' ') {
      stop = true;
  }
  if (key == 'a' || key == 'A') {
      attract = true;
  }
  if (key == 'x' || key == 'X') {
      showText = false;
  }
}  

function keyReleased() {
  if (key == ' ') {
      stop = false;
  }
  
  if (key == 'a' || key == 'A') {
      attract = false;
  }
}

function makeParticle(px, py, pdx, pdy) {
    p = {x: px, y: py,
         dx: pdx, dy: pdy,
         particleCaptured: false,
         orbit: 5,
         modifier: 1,
         step: particleStep,
         draw: particleDraw
        }
    return p;
}

Looking Outwards 12 Ellan Suder

When searching for motion comics, I found this scrolling style comic — it is a compilation of animated horror comics called “Chiller” (2015) by several different artists. It uses the scroll to trigger the effects.

Music starts to play when you scroll to this part and the title appears

I think this is a really interesting way to adapt horror comics. It’s kind of a middle ground between regular static comics and horror video games, where it’s still linear but gives a little control to the reader. The reader controls the story instead of passively watching like they would for a horror movie. By including this interactivity in the reading experience, the reader is more immersed in the story and hopefully more susceptible to fear.

The sounds were the creepiest part of the comic for me because of how sudden they were, like auditory jumpscares. They added to the dread the reader feels when scrolling further down the page.

The second motion comic I found was also controlled by scrolling, but this time it moved horizontally. The comic is an informational comic about The Walking Dead’s BTS makeup effects. Scrolling animates the character to make him walk forward through the buildings, where info about the show will appear. There are also clickable elements, play buttons for videos that reveal themselves as they scroll.

The play button in the closet doesn’t seem to be working unfortunately
You can scroll back and forth to make the animation play or reverse

It’s a really fun and interesting way to present information, as opposed to maybe an article or long video. The feature I liked most was the reversibility. Instead of the scroll triggering an effect and then ending there, the scroll was actually directly tied to the motion itself. So in the ending scene where the zombie is shot, you could control the animation frame by frame by scrolling.

Ellan Suder Project Proposal

Watchmen Motion Comic
The Accountant Motion Comic

For my final project I would like to do a motion/animated comic for a poem (perhaps the one below?) or some short story. The illustrations will be drawn by me and imported into the program from imgur. The viewer interacts with the comic by clicking.

Fog

The fog comes
on little cat feet.
 
It sits looking
over harbor and city
on silent haunches
and then moves on.
Storyboard for poem (new frame for every line break)

Features I found interesting and wanted to use:

  1. Separately moving backgrounds and characters (motion parallax) give sense of depth.
  2. Character movement (maybe something like an arm waving or eyes moving).
  3. Blinking lights.
  4. Could add in sound effects but I’m more interested in the visuals

Project 11 Ellan Suder

I wanted to make a landscape that gave the illusion of depth, so objects would be moving past at different speeds depending on ‘how far back’ they are. I started by creating the mountains using the noise function. They were initially straight lines (x values i and i), but I used i*0.8 and i instead because the offset created an interesting effect. Then I made a balloon that randomly picked an image from an array. This balloon also randomly generated things like speed and height.

Initial code for balloon. It would appear from the left and, after reaching end of canvas, would reappear with different randomized image/location/speed

After ironing things out a bit, I used the sample code as a framework for making several balloons in an array. I also made the speed/size more deliberate. Now, the closer the balloon is to the top (the smaller the y-value), the slower it moves, giving the illusion that it is farther away.

sketch

/*
Ellan Suder
15104 1 D
esuder@andrew.cmu.edu
Project-11
*/

var balloons = [];
var balloonLinks = [];
var t1 = 0;
var t2= 0;
var t3 = 0;


function preload() {
    balloonLinks = [
    "https://i.imgur.com/rmLvmIq.png",
    "https://i.imgur.com/03Cr2Sx.png", 
    "https://i.imgur.com/peP166r.png"];
}


function setup() {
    createCanvas(480, 480); 
    frameRate(30);
  
    // create an initial collection of balloons
    for (var i = 0; i < 10; i++){
        var rx = random(-180, width);
        var ry = random(40,height-200);
        var rs = 70*(0.005*ry); //make balloons higher up smaller
        var spd = ry/70; //make balloons higher up move slower
        randomBalloon = floor(random(balloonLinks.length));
        balloon = loadImage(balloonLinks[randomBalloon]);
        balloons[i] = makeBalloon(balloon, rx, ry, rs, spd);
    }
    frameRate(10);
}

function draw() {
    background(200); 
  
    updateAndDisplayBalloons();
    removeBalloonsThatHaveSlippedOutOfView();
    addNewBalloonsWithSomeRandomProbability();
    
    //the larger t is
    //the faster it moves
    t1 += 0.01;
    t2 += 0.04;
    t3 += 0.1;
  
  
    for(var i=0; i < width*1.3; i++)
    {
    //the larger the number you divide i by
    //the 'faster' the terrain will move
    n1 = noise(i/100-t1);
    n2 = noise(i/100-t2);
    n3 = noise(i/100-t3);
    
    stroke(0,20);
    line(i*0.8, height*n1, //top of the line (bumpy randomized part)
         i, height); //bottom of line is bottom of canvas
    
    stroke(0,70);
    line(i*0.8, height*n2,
         i, height);
      
    stroke(0,200);
    line(i*0.8, height*n3,
         i, height);
    }
}

function updateAndDisplayBalloons(){
   // Update the balloon's positions, and display them.
    for (var i = 0; i < balloons.length; i++){
        balloons[i].move();
        balloons[i].display();
    }
}

function removeBalloonsThatHaveSlippedOutOfView(){
    // If a balloon has dropped off the left edge,
    // remove it from the array.
    var balloonsToKeep = [];
    for (var i = 0; i < balloons.length; i++){
        if (balloons[i].x + balloons[i].size > 0) {
            balloonsToKeep.push(balloons[i]);
        }
    }
    balloons = balloonsToKeep; // remember the surviving balloons
}


function addNewBalloonsWithSomeRandomProbability() {
    var newBalloonLikelihood = 0.03; 
    if (random(0,1) < newBalloonLikelihood) {
        rx = random(-60, -50);
        ry = random(40,height-200);
        rs = 70*(0.005*ry);
        spd = ry/70; //make balloons higher up move slower
        randomBalloon = floor(random(balloonLinks.length));
        balloon = loadImage(balloonLinks[randomBalloon]);
        balloons.push(makeBalloon(balloon, rx, ry, rs, spd));
    }
}

// method to update position of balloon every frame
function balloonMove() {
    this.x += this.speed;
}

// draw the balloon
function balloonDisplay() {
    image(this.img,this.x,this.y,this.size,this.size);
}

function makeBalloon(balloon, balloonX, balloonY, balloonSize, balloonSpeed) {
    var blln = {img: balloon,
                x: balloonX,
                y: balloonY,
                size: balloonSize,
                speed: balloonSpeed,
                move: balloonMove,
                display: balloonDisplay}
    return blln;
}

Ellan Suder Project 10: Interactive Sonic Sketch

When you click the program, it generates a random cat and plays the corresponding sound. At first I tried putting all the sound links in an array and using sound = loadImage(catSounds[randomCat]); like I did with the cat images, but it didn’t load fast enough and I ended up needing to load every sound at the beginning manually.

happy halloween!

var catLinks;
var catSounds;
var catInd;

function preload() {
    angrySound = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/11/es_angrymeow.wav");
    regularSound = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/11/es_meow.wav");
    scaredSound = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/11/es_hiss.wav");
    happySound = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/11/es_purr.wav");
    
    angrySound.setVolume(1);
    regularSound.setVolume(1);
    scaredSound.setVolume(1);
    happySound.setVolume(1);

    catSounds = [
    angrySound,
    regularSound,
    scaredSound,
    happySound];

    catLinks = [
    "https://i.imgur.com/NxERNed.png", //angry
    "https://i.imgur.com/OX62vqE.png", //regular
    "https://i.imgur.com/uba4Kxv.png", //scared
    "https://i.imgur.com/pZwffK9.png"]; //happy
    
    //chooses random array number
    randomCat = floor(random(catLinks.length));
    //loads random cat image from array
    cat = loadImage(catLinks[randomCat]);
}


function setup() {
    createCanvas(300, 300);
}

function draw() {
    background(255, 90, 0);
    image(cat,0,0);
}

function mousePressed() {
    //catInd records last randomCat array number
    catInd=randomCat;
    //If array number is same, pick new one. 
    //Don't show same cat in a row.
    while(randomCat==catInd)
    {
      randomCat = floor(random(catLinks.length));
    }

    cat = loadImage(catLinks[randomCat]);
    catSounds[randomCat].play();
}

Looking Outwards 10 Ellan Suder

Generative Music by batuhan

Software used: Processing connected to SuperCollider via osc_p5 library and the great p5_sc library, and I used the Atari2600 UGen by Fredrik Olofsson, all custom software.

“Generative Music – Cellular automata and blip blops” (2008) by batuhan is a “somewhat intelligent cellular automata system” developed with some atari2600 style sonification. The music is generated by a cellular life system — the artist does not touch the system once it’s started and it dies by itself, ending the music as well. The thing I really liked about the project was the idea of creating a random, computer generated piece of music tied to the lifespan of a system. The song is the representation of a world from its beginning to its end.

The visuals and the audio (the harsh ‘blip blops’) seem to be inspired by old classic arcade style video games.

Looking Outwards 11 Ellan Suder

Short Biography of Angela Washko: Angela Washko has a BFA in painting/drawing/sculpture and an MFA in Visual Art. She currently works as a Visiting Assistant Professor of Art at Carnegie Mellon. Broadly speaking, her work focuses on feminist issues, “creating new forums for discussions of feminism in spaces frequently hostile toward it.” For example, she has operated The Council on Gender Sensitivity and Behavioral Awareness in World of Warcraft since 2012.

“The Game” by Angela Washko is a dating simulator video game about pick-up artists, where the player is a woman being aggressively pursued by 6 men that attempt to. The dialogue is the strongest part of the game, but the rough graphics, almost horror-like close-ups, and intense music add to the disturbing quality of the experiences.

Gameplay still

The player can choose between several responses that range from positive, where you accept and play into the PUA’s techniques, and negative, where you rebuke the PUA’s advances.

I really like the game as it is now. I think Washko could lean in further to the ‘dating simulator’ aspect. Right now it presents the choices in a fairly equal and straightforward manner, so most may just reflexively choose the options where you refuse the PUA’s, but adding an in-game scoreboard/consequences to each choice (for example, if you act ‘rudely’ and refuse a man you lose ‘social standing’) would add to the suppressive awkwardness of the exchanges and perhaps make the player feel more self-conscious of choosing the options where you don’t play along.

Project 09 Ellan Suder

I don’t have any paper sketches, but I have some screenshots of the earlier versions. I wanted to make it “print” from left to right and go down to a new row once it reaches the end (width). When it reaches the end of the canvas (width, height), it moves back to 0,0 and starts printing the image again.

I wanted to add some interactivity using mousePressed. Every time the mouse is clicked:

  1. A number between 1 and 20 is added to the step and size arrays.
  2. i increases by 1, so that it accesses the newly generated elements in the arrays.

computational portrait (please click!)

/*
Ellan Suder
15104 1 D
esuder@andrew.cmu.edu
Project-09
*/

var underlyingImage;
var rectX = 0;
var rectY = 0;
var i = 0;
rectstep = [5];
rectsize = [5];

function preload() {
    var myImageURL = "https://i.imgur.com/frUuo2H.png";
    underlyingImage = loadImage(myImageURL);
}

function setup() {
    createCanvas(480, 480);
    background(0);
    underlyingImage.loadPixels();
    frameRate(10000);
}

function draw() {
    var theColorAtLocationXY = underlyingImage.get(rectX, rectY);

    noStroke();
    fill(theColorAtLocationXY);
    rect(rectX, rectY, rectsize[i], rectsize[i]);
  
    rectX += rectstep[i];
  
//rectX return to 0 when hit edge of canvas
    if (rectX >= width)
    { 
      rectX = 0;
      rectY += rectstep[i];
    }
//restarts when hits end of canvas
    if (rectY >= height)
    { 
      rectX = 0;
      rectY = 0;
      background(0);
    }
}

function mousePressed() {
    var r = random(1,20);
    
    rectstep.push(r);
    rectsize.push(r);
    i += 1;
}

Week 9 Looking Outwards

Siwei Xie’s Looking Outwards 06 assignment is about Richter, a German visual artist, who created “4900 Colours: Version II” (2008). The colors used in this series are generated randomly from a palette of 25 colors by a computer program. Xie said: “Creator’s artistic sensibility manifests by how ‘non-random’ the panels look, with some dominated by particular colors which are often placed next to each other. But the whole point of ‘pure’ randomness is that apparent patterns are expected to occur.”

Two panels from the exhibit

To add on, this reminded me of an interesting thing I learned about how the ‘random’ shuffle function for music is not actually random. When they are actually purely random, people will feel like they can detect patterns in even the smallest coincidences — such as when certain songs or artists come after one another.

To deal with this ‘nonrandom’-feeling randomness, Spotify changed its algorithms to feel more random to humans. Instead of using the Fisher-Yates shuffle, which people complained wasn’t genuinely random, Spotify updated its algorithm to distribute artists and genres more evenly. For example, “if there are four songs by the White Stripes in a playlist … the algorithm will aim to play them at roughly 25% intervals.” I think it’s very interesting how much the human tendency to detect patterns can affect the way we interpret randomness.

Project 7 Ellan Suder

These are different variations of the epitrochoid function. The number of points increases as mouseX increases, so as the mouse drags across from left to right the shape ‘unfolds’ from a line to a triangle to a polygon with several sides. As mouseY increases, this polygon into a more irregular shape dictated by the epitrochoid function. There are three of these shapes, each filled differently (white, black, random color) and with slight variations so that they don’t overlap perfectly. The background circles increase as theta/t/mouseX increases.

sketch

/*
Ellan Suder
15104 1 D
esuder@andrew.cmu.edu
Project-07
*/

var randomColor;
var noiset = 0;
var nPoints = 10;

function setup() {
  createCanvas(480, 480);
  noStroke();
}

function draw() {
    noiset+=0.01;
    randomColor = color(noise(noiset+ 180)*205,noise(noiset+160)*205,noise(noiset+60)*205);
    background(0);
  
    push();
    translate(width / 2, height / 2);
    var x;
    var y;
    var nPoints = 2 + mouseX / 40;
    var a = width/5;
    var b = a/5;
    var h = constrain(mouseY/20, 0, b);
    var ph = mouseX / 50.0;
    
    fill(30);
    for (var i = 0; i < nPoints; i++) {
        var t = map(i, 0, nPoints, 0, TWO_PI);
        var px = 300 * cos(t);
        var py = 350 * sin(t);
        ellipse(px, py/6, px/5, py/10);
        ellipse(py/6, px, px/10, py/5);
    }
    
    fill(randomColor);
    beginShape();
    for (var i = 0; i < nPoints; i++) {
        var t = map(i, 0, nPoints, 0, TWO_PI);
        
        x = (a + b) * cos(t) - h * cos(2 * ph + t * (a + b) / b);
        y = (a + b) * sin(t) - h * sin(t * (a + b) / b);
        vertex(x, y);
    }
    endShape(CLOSE);
  
    fill(0);
        beginShape();
    for (var i = 0; i < nPoints; i++) {
        var t = map(i, 0, nPoints, 0, TWO_PI);
        
        x = (a + b) * cos(t) - h * cos(t * (a + b) / b);
        y = (a + b) * sin(t) - h * sin(ph + t * (a + b) / b);
        vertex(x, y);
    }
    endShape(CLOSE);

    fill(255);
        beginShape();
    for (var i = 0; i < nPoints; i++) {
        var t = map(i, 0, nPoints, 0, TWO_PI);
        
        x = (a + b) * cos(t) - h * cos(t * (a + b) / b);
        y = (a + b) * sin(t) - h * sin(2 * ph + t * (a + b) / b);
        vertex(x, y);
    }
    endShape(CLOSE);
    pop();
}