Angela Lee – Final Project

Instructions: Please expand your browser window as much as possible since my canvas is at the width of 600! Thank you 🙂
To interact with my speculative comic panel, here are a couple of interactions you can try.
To speed up the subway car, press the “f” key.
To slow down the subway car, press the “s” key. (Note: you cannot make the subway go backwards. If you press this key too many times, it will reset to its original speed.)
To reset the subway car speed, press the “r” key.
In frames 1 & 2, click your mouse around to see some falling stars.

sketch

/*
 * Angela Lee
 * Section E
 * ahl2@andrew.cmu.edu
 * Final Project
 */

// FRAME 1 VARIABLES
var f1height = 270; // height of first frame
var f1width = 275; // width of first frame
var f1x = 22.5; // upper left x-pos of first frame
var f1y = 12.5; // upper left y-pos of first frame
var f1spacing = 9; // spacing to the right of the first frame
var accelerate = 0; // how much the subway car speeds up/slows down
var skyBlue; // top of the sky color
var skyYellow; // bottom of the sky color
var waterYellow; // top of the water color
var waterBlue; // bottom of the water color
var shootingStarArray = []; // array for shooting stars
var ripples = []; // array for the ripples

// FRAME 2 VARIABLES
var f2width = 270; // width of second frame
var f2height = 130; // height of second frame
var f2spacing = 11.5; // spacing below the second frame
var planetx = 400; // x position of the big planet
var subwayStarArray = []; // array for subway stars


function setup(){
	createCanvas (600, 300);
    background(27, 34, 45);

    // gradient for the sky in frame 1
    skyBlue = color(2, 76, 98); // top of the sky color
    skyYellow = color(233, 255, 191); // bottom of the sky color

    // gradient for the water in frame 1
    waterYellow = color(252, 239, 194); // top of the water color
    waterBlue = color(59, 98, 115); // bottom of the water color

    sub = makeSubway(); // creates a subway object in frame 1

    // RIPPLES IN FRAME 1
    // boundaries for ripples in frame 1
    var top = 212; // top boundary
    var bottom = height; // bottom boundary
    // first ripples that come into frame 1
    for (var i = 0; i < 10; i++) {
        var rippleX = random(width);
        var rippleY = random(top, bottom);
        ripples[i] = makeRipples(rippleX, rippleY);
    }
}

function draw(){
    // FRAME 1
    f1scene(); // the sky
    // moving the subway
    sub.move();
    sub.draw();
    // moving the ripples
    for (var i = 0; i < ripples.length; i++) {
        ripples[i].move();
        ripples[i].draw();
    }
    removeRipple();
    addRipple(); 


    // FRAME 2
    f2scene(); // background in frame 2
    // moving the shooting stars
    for (var i = 0; i < shootingStarArray.length; i++) {
        shootingStarArray[i].move();
        shootingStarArray[i].draw();
    }
    removeShootingStar(); // keeps shooting stars that are in frame
    addShootingStar(); // adds shooting stars when mouse is clicked
    f2car(); // subway car features in frame 2
    astroboy(); // astronaut boy in frame 2


    // FRAME 3
    f3scene(); // background in frame 3


    // TEXT
    // words in frame 1
    var f1text = "every night i take the subway home..."
    // words in frame 2
    var f2text = "i wonder..."
    // words in frame 3
    var f3text = "if our worlds will ever collide again."
    noStroke();
    fill("white");
    textSize(14);
    textStyle(ITALIC);
    text(f1text, f1x + 10, 279); // frame 1 text
    text(f2text, 490, 87); // frame 2 text
    text(f3text, f1x + f1width + 25, 279); // frame 3 text


    // BORDERS AROUND THE FRAMES
    noStroke();
    fill(27, 34, 45);
    rect(0, 0, width, f1y); // top border
    rect(0, 0, f1x, height); // border left of frame 1
    rect(f1x + f1width, 0, 17, height); // border right of frame 1
    rect(0, 0, f1x + f1width, f1y); // border above frame 1
    // border below frame 1
    rect(0, f1y + f1height, width, width - f1y - f1height);
    // border below frame 2
    rect(f1x + f1width, f1y + f2height, width - f1x - f1width, f2spacing);
    // border right of frame 2
    rect(f1x + f1width + f1spacing + f2width, 0, 
        width - f1x + f1width + f1spacing + f2width, height);
}

// ---------------- FUNCTIONS & OBJECTS FOR FRAME 1 ------------------

// FRAME 1 BACKGROUND
function f1scene(){
    // GRADIENT SKY
    noFill();
    for (var s = f1y; s < f1y + f1height; s++) {
        var skyInter = map(s, f1y, f1y + f1height, 0, 1);
        var skyStroke = lerpColor(skyBlue, skyYellow, skyInter);
        stroke(skyStroke);
        line(f1x, s, f1x + f1width, s);
    } 
    planets(); // planets in the sky
    surface(); // surface of the moon
    clouds(); // clouds in the sky
    constellations(); // constellations in the sky 

    // TWINKLING STARS
    fill(242, 242, 158);
    for (var st = 0; st < 8; st++) {
        // x positions for the twinkling stars
        starX = [32, 77, 129, 177, 190, 205, 242, 276];
        // y positions for the twinkling stars
        starY = [91, 66, 63, 27, 79, 53, 25, 90];
        // sizes for the twinkling stars
        starSize = [5, 4, 4, 5, 4, 5, 4, 5];
        star(starX[st], starY[st], 
             starSize[st] - random(1, 3), starSize[st], 5);
    }

    // SUBWAY TRACK
    stroke(79, 94, 94);
    fill(130, 142, 134);
    // pillars holding the track
    for (var i = 0; i < 6; i++) {
        rect(34 + i * 48, 148, 14, 67);
    }
    rect(0, 148, f1x + f1width, 13); // bridge above the water

    // GRADIENT WATER
    noFill();
    for (var w = 212; w < f1y + f1height; w++) {
        var waterInter = map(w, 212, f1y + f1height, 0, 1);
        var waterStroke = lerpColor(waterYellow, waterBlue, waterInter);
        stroke(waterStroke);
        line(f1x - 1, w, f1x + f1width, w);
    }
}

// CONSTELLATIONS iN FRAME 1
function constellations() {
    stroke(131, 178, 177); 
    strokeWeight(1);

    // LIBRA CONSTELLATION
    // the following lines join together to create libra
    line(98.5, 90.5, 98.5, 84.5);
    line(98.5, 84.5, 89.5, 70.5);
    line(89.5, 70.5, 111.5, 60.5);
    line(111.5, 60.5, 112.5, 74.5);
    line(89.5, 70.5, 96.5, 56.5);
    line(96.5, 56.5, 111.5, 60.5);

    // LEO CONSTELLATION
    // the following lines join together to create leo
    line(146, 84, 142, 75);
    line(142, 75, 154, 79);
    line(154, 79, 146, 84);
    line(146, 84, 148, 97);
    line(148, 97, 156, 95);
    line(156, 95, 156, 89);
    line(156, 89, 161, 88);
    line(161, 88, 172, 95);
    line(172, 95, 169, 100);

    // AQUARIUS CONSTELLATION
    // the following lines join together to create aquarius
    line(258, 49, 241, 63);
    line(241, 63, 246, 65);
    line(246, 65, 254, 63);
    line(241, 63, 241, 67);
    line(241, 67, 239, 69);
    line(239, 69, 247, 83);
    line(247, 83, 249.5, 77.5);
    line(249.5, 77.5, 258, 76);
    line(258, 76, 268, 82);

}

// CLOUDS IN FRAME 1
function clouds() {
    noStroke();

    // YELLOW CLOUDS
    fill(210, 216, 130, 70);
    var ycloudx = [92, 67, 103, 117, 122, 159]; // x coordinates
    var ycloudy = [92, 88, 53, 56, 52, 70]; // y coordinates
    var ycloudw = [56, 48, 27, 35, 19, 25]; // cloud width
    var ycloudh = [9, 8, 5, 4, 3.5, 3]; // cloud height
    // for loop generates 6 yellow clouds
    for (var y = 0; y < 6; y++) {
        ellipse(ycloudx[y], ycloudy[y], ycloudw[y], ycloudh[y]);
    }

    // PINK CLOUDS
    fill(242, 136, 158, 150);
    var pcloudx = [251, 65, 48, 183, 196, 212]; // x coordinates
    var pcloudy = [109, 58, 55, 43, 46, 41]; // y coordinates
    var pcloudw = [80, 30, 25, 35, 31, 29]; // cloud width
    var pcloudh = [12, 7, 3, 4, 7, 9]; // cloud height
    // for loop generates 6 pink clouds
    for (var p = 0; p < 6; p++) {
        ellipse(pcloudx[p], pcloudy[p], pcloudw[p], pcloudh[p]);
    }
}

// SURFACE OF THE MOON IN FRAME 1
function surface() {
    strokeWeight(1);
    stroke(31, 73, 72);
    fill(38, 104, 119);

    // x positions of the moon surface's shape
    var moonx = [f1x, 61, 107, 152, 202, 248, f1x + f1width, 
                 f1x + f1width, f1x];
    // y positions of the moon surface's shape
    var moony = [187, 185, 191, 180, 188, 190, 195, 237, 237];

    // SURFACE OF THE MOON
    beginShape(); 
    vertex(moonx[0], moony[0]);
    // for loop for curved parts of the shape
    for (var m = 0; m < 7; m++) {
        curveVertex(moonx[m], moony[m]);
    }
    // straight parts of the moon surface shape
    // these points can't be seen because they are
    // blocked by the water
    vertex(moonx[7], moony[7]);
    vertex(moonx[8], moony[8]);
    vertex(moonx[0], moony[0]);
    endShape();

    // MOON CRATERS
    noStroke();
    fill(22, 81, 91);
    ellipse(58, 192, 29, 6);
    ellipse(96, 202, 19, 3);
    ellipse(146, 188, 19, 4);
    ellipse(201, 201, 20, 3);
    ellipse(241, 200, 34, 5);
}

// PLANETS IN THE SKY OF FRAME 1
function planets () {
    // LARGEST PLANET
    strokeWeight(2);
    stroke(30, 76, 114);
    fill(32, 111, 142);
    ellipse(217, 164, 142, 142);

    // SECOND LARGEST PLANET
    strokeWeight(1);
    stroke(24, 76, 119);
    fill(18, 114, 130, 95);
    ellipse(95, 119, 75, 75);

    // RINGED PLANET
    noStroke();
    fill(234, 106, 124);
    ellipse(63.5, 45, 51, 12); // rings
    strokeWeight(0.5);
    stroke(140, 65, 83);
    fill(242, 136, 158);
    ellipse(64, 44, 25, 25); // planet itself

    // CRESCENT MOON
    strokeWeight(1);
    stroke(234, 96, 119);
    fill(242, 242, 158);
    ellipse(144.8, 40, 30, 30); // crescent part
    noStroke();
    fill(31, 94, 104);
    ellipse(143, 37, 24, 24); // shadow part

}

// MAKING THE STARS IN FRAME 1 + 3
// called in f1scene() and f3scene()
function star(x, y, radius1, radius2, npoints) {
    var angle = TWO_PI / npoints;
    var halfAngle = angle / 2.0;
    beginShape();
    for (let a = 0; a < TWO_PI; a += angle) {
        var sx = x + cos(a) * radius2;
        var sy = y + sin(a) * radius2;
        vertex(sx, sy);
        sx = x + cos(a + halfAngle) * radius1;
        sy = y + sin(a + halfAngle) * radius1;
        vertex(sx, sy);
    }
  endShape(CLOSE);
}

// ------------- FUNCTIONS & OBJECTS FOR FRAME 2 -------------------

// FRAME 2 BACKGROUND
function f2scene() {
    // WINDOW BEHIND THE RIDER
    strokeWeight(4);
    stroke(255);
    fill(9, 74, 89);
    rect(302, 29, 291, 85);

    // NIGHTTIME SCENERY
    // stars that come into frame when the subway is "moving"
    noStroke();
    fill(242, 242, 158);
    for (var i = 0; i < subwayStarArray.length; i++) {
        subwayStarArray[i].move();
        subwayStarArray[i].draw();
    }
    removeSubwayStar(); // keeps stars in the keepSubwayStar array
    addSubwayStar(); // based on a small probability, adds new stars

    // big planet
    strokeWeight(2);
    stroke(30, 76, 114);
    fill(32, 111, 142);
    ellipse(planetx, 100, 130, 130); 
    planetx += 0.05 // planet slowly moves right as time passes

    // MORE WINDOW FEATURES
    // glass dividers on the subway windows
    strokeWeight(3);
    stroke(255);
    var divideh = 24; // height of vertical dividers
    var dividex = 335; // initial x position of vertical dividers
    var dividey = 32; // y position of all vertical dividers
    var dividespace = 70; // horizontal spacing in between
    // 4 vertical dividers are created through lines
    for (var d = 0; d < 4; d++) {
        line(dividex + dividespace * d, dividey,
             dividex + dividespace * d, dividey + divideh);
    }
    // horizontal line beneath the vertical dividers
    line(302, 32 + divideh, 302 + f2width, 32 + divideh);

    // window reflections
    noStroke();
    fill(253, 255, 255, 40);
    // first reflection shape
    beginShape();
    vertex(330, 112);
    vertex(326, 95); 
    vertex(381, 31);
    vertex(399, 31);
    vertex(330, 112);
    endShape();
    // second reflection shape
    beginShape();
    vertex(355, 95);
    vertex(409, 31);
    vertex(418, 31);
    vertex(364, 95);
    vertex(355, 95);
    endShape();
    // third reflection shape
    beginShape(); 
    vertex(423, 98);
    vertex(472, 30);
    vertex(509, 30);
    vertex(465, 98);
    vertex(423, 98);
    endShape();
    // fourth reflection shpae
    beginShape();
    vertex(512, 102);
    vertex(555, 31);
    vertex(572, 31);
    vertex(531, 102);
    vertex(512, 102);
    endShape();
}

// FEATURES OF THE SUBWAY CAR IN FRONT OF THE WINDOW
function f2car() {
    // subway car wall above the window
    noStroke();
    fill(143, 169, 183);
    rect(f1x + f1width + 10, 0, f2width, 29);
    // subway car wall below the window
    rect(f1x + f1width + 10, 114, f2width, 40);

    // subway chairs
    // back of the chair that the rider leans against
    stroke(73, 122, 121);
    strokeWeight(1);
    fill(174, 199, 206);
    rect(320, 92, 247, 77, 15); 

    // indents in the chair 
    noStroke();
    fill(153, 181, 188); 
    var indentx = 333; // initial x positions of indents
    var indenty = 103; // y positions of indents
    var indentw = 66; // width of indents
    var indenth = 65; // height of indents
    var indentround = 15; // roundness of indents
    var indentspacing = 79; // spacing between each indent
    // for loop creates 3 evenly spaced indents in the chair 
    for (var c = 0; c < 3; c++) {
        rect(indentx + indentspacing * c, indenty, indentw, 
             indenth, indentround);
    }

    // handles above the window
    noFill();
    strokeWeight(3);
    stroke(200, 214, 219);
    var handlespacing = 82; // horizontal spacing between handles
    // for loop creates 3 evenly spaced handles
    for (var h = 0; h < 3; h++) {
        ellipse(360 + handlespacing * h, 20, 10, 40);
    }
}

// ASTRONAUT BOY SITTING IN THE SUBWAY
function astroboy() {
    // arms
    noStroke();
    fill(160, 116, 87);
    rect(397, 137, 13, 15); // right arm

    // shirt
    fill(194, 160, 224);
    ellipse(379, 118.5, 66, 19); // shoulders
    rect(356, 117, 47, 35); // body
    rect(346, 119, 15, 19); // left sleeve
    ellipse(354, 137.5, 16, 5); // bottom of left sleeve
    rect(400, 119, 12, 19); // right sleeve
    ellipse(404, 137.5, 16, 5); // bottom of right sleeve
    // pocket on shirt
    fill(234, 132, 132); 
    rect(385, 130, 11, 13); 
    ellipse(390, 143, 11, 3);

    // right arm
    noStroke();
    fill(160, 116, 87);
    quad(346, 138, 360, 138, 366, 150, 352, 150); // left arm

    // astronaut helmet
    // neck piece
    noStroke();
    fill(100);
    ellipse(377, 111, 27, 6);
    rect(364, 100, 27, 11);
    // helmet
    strokeWeight(1);
    stroke(184, 209, 208);
    fill("white");
    ellipse(377, 83, 48, 48);
    // glass piece
    noStroke();
    fill(33, 51, 68);
    ellipse(376, 95.5, 34, 11);
    quad(357, 85, 395, 85, 393, 96, 359, 96);
    ellipse(376, 85, 38, 11);
    // highlights on glass piece
    fill(100, 107, 109);
    ellipse(371, 84, 22, 2); // top highlight
    ellipse(376, 98, 18, 1); // bottom highlight
}

// ------------------ FUNCTIONS/OBJECTS IN FRAME 3 ---------------------

// FRAME 3 BACKGROUND
function f3scene() {
    // the dark night sky
    noStroke();
    fill(18, 38, 76);
    rect(307, 12 + f2height + f2spacing, f2width, f2height); 

    // clouds
    // darkest clouds
    fill(39, 49, 117, 170);
    ellipse(382, 193, 69, 48);
    ellipse(534, 201, 95, 47);
    // purple clouds
    fill(118, 86, 153, 120);
    ellipse(348, 230, 107, 71);
    ellipse(445, 220, 142, 77);
    ellipse(531, 225, 98, 52);
    // darker pink clouds
    fill(219, 127, 154, 130);
    ellipse(374, 247, 76, 50);
    ellipse(520, 251, 135, 50);

    // glow behind the star in figure's hand
    noStroke();
    var glowx = 491; // x position of the glow center
    var glowy = 216; // y position of the glow center 
    // controls how the glowing portion of the star 
    // grows and shrinks, so it looks like it's flickering
    var osc = 10 + sin(millis()) * 1;
    // outer glow
    fill(239, 125, 172, 100);
    ellipse(glowx, glowy, 20 + osc, 20 + osc);
    // middle glow
    fill(249, 155, 189, 120);
    ellipse(glowx, glowy, 10 + osc, 10 + osc);
    // inner glow
    fill(237, 102, 138);
    ellipse(glowx, glowy, 4 + osc, 4 + osc);

    starfigure(); // mysterious figure in the clouds

    // light pink clouds
    noStroke();
    fill(242, 165, 165, 200);
    ellipse(337, 273, 76, 50);
    ellipse(535, 276, 94, 50);
    fill(242, 165, 165); // cloud covering the figure is opaque
    ellipse(430, 278, 135, 50);


    // twinkling stars
    fill(242, 242, 158);
    for (var st = 0; st < 5; st++) {
        // x positions for the twinkling stars
        starX = [366, 413, 491, 493, 514];
        // y positions for the twinkling stars
        starY = [240, 213, 216, 245, 178];
        // sizes for the twinkling stars
        starSize = [8, 9, 6, 8, 6];
        star(starX[st], starY[st], 
             starSize[st] - random(4, 5), starSize[st], 5);
    }
}

function starfigure() {
    // body
    fill(224, 184, 140);
    ellipse(466, 195.5, 2, 5); // right ear
    ellipse(443, 194.5, 2, 5); // left ear
    rect(450, 203, 8, 9); // neck
    quad(470, 235, 485, 219, 490, 220, 475, 240); // arm
    ellipse(488.5, 219.5, 7, 3); // palm
    //forefinger
    beginShape();
    vertex(491, 220);
    vertex(491, 218);
    vertex(494.2, 217.6);
    vertex(494.5, 218.5);
    vertex(492, 220);
    vertex(491, 220);
    endShape();
    ellipse(494.5, 216.5, 1, 4);// upper finger

    //hair
    fill(248, 252, 195);
    ellipse(454.5, 194, 23, 24);
    hairx = [443, 466, 464, 463, 462, 452, 450, 449, 445, 445, 
             444, 441, 443, 443]; // x positions of hair coordinates
    hairy = [194, 194, 208, 205, 208, 208, 206, 208, 207, 205, 
             207, 207, 199, 194]; // y positions of hair coordinates
    beginShape();
    for (var h = 0; h < 14; h++) {
        vertex(hairx[h], hairy[h]);
    }
    endShape();

    // cape
    fill(30, 30, 124);
    ellipse(454, 217, 35, 13);
    quad(426, 266, 437, 218, 472, 218, 482, 266);
    // folds in the cape
    fill(16, 16, 94);
    triangle(438, 259, 443, 221, 443, 259);
    triangle(467, 260, 465, 222, 472, 260);
    // constellations of the cape
    strokeWeight(1);
    stroke(113, 108, 183);
    // first constellation
    line(443, 216, 448, 224);
    line(448, 224, 452, 225);
    line(452, 225, 445, 235);
    // second constellation
    line(455, 236, 453, 244);
    line(453, 244, 459, 246);
    line(459, 246, 461, 242);
    line(461, 242, 465, 250);
    // constellation on the edge of the cape
    line(432, 243, 434, 245);
    line(434, 245, 429, 251);
}

// ------------- FUNCTIONS & OBJECTS FOR THE MOVING SUBWAY IN FRAME 1 -------------

//  PRESSING KEYS TO CONTROL SUBWAY CAR SPEED
function keyPressed() {
    // everytime the "f" key is pressed, the subway car's 
    // speed will increase by 1
    if (key == "f") {
        accelerate += 1;
    }

    // likewise, everytime "s" is pressed, the subway car's
    // speed will reduce by 1
    if (key == "s") {
        accelerate -= 1; 
    }

    // upon pressing the r key, it resets the accelerate to 0
    // helpful in case you make the train way too fast and don't
    // want to click s multiple times to get back to the original speed
    if (key === "r") {
        accelerate = 0; 
    }
}

// SUBWAY OBJECT
function makeSubway() {
    var subwaycar = {x: -200,
                     y: 119,
                     speed: 4,
                     move: moveSubway,
                     draw: drawSubway}
    return subwaycar; 
}

function drawSubway() {
    // subway cars
    var sublength = 75; // length of subway car
    var subheight = 28; // height of subway car
    var subx = -200; // x position of subway car
    var suby = 119; // y position of subway car
    var round = 7.5; // rounded edges of rect

    // subway car windows
    var windowY = 125; // y position of subway window
    var windowW = sublength / 4 // length of subway window
    var windowH = 10; // height of subway window
    var wspace = 4.5; // spacing between subway windows

    // creating the subway car
    for (var s = 0; s < 5; s++) {
        stroke(125, 121, 132);
        fill(230, 230, 252); 
        rect(this.x + sublength * s, suby, sublength, subheight, round);
        for (var i = 0; i < 3; i++) {
            fill(21, 68, 76);
            rect(this.x + wspace * (i + 1) + windowW * i + sublength * s, 
                windowY, windowW, windowH);
        }
    }
}

function moveSubway() {
    this.x += (this.speed + accelerate); 
    // to prevent the subway car from moving backwards, 
    // if accelerate becomes too big that its absolute value
    // is greater than the speed, it resets to 0, which means
    // the subway train will pause right before and upon pressing
    // the s key again, it will reset to the original speed
    if (accelerate < this.speed * -1) {
        accelerate = 0;  
    }
    // reset the x position of the last subway car to -500
    // if the last subway car exits the frame
    if (this.x > f1x + f1width) {
        this.x = -500; 
    }
}

// ----------- FUNCTIONS & OBJECTS FOR FALLING STARS IN FRAME 1 & 2 ----------

// FALLING STARS IN FRAME 1 & 2 WHEN MOUSE IS PRESSED
function mousePressed() {
    // when mouse is pressed, the function will 
    // to add a shooting star will be called with 
    // the x and y inputs of the mouse position
    if (mouseX < width && mouseX > f1x &&
        mouseY < f1y + f1height && mouseY > f1y) {
        addShootingStar(mouseX, mouseY);
    }
}

// SHOOTING STAR OBJECT 
function makeShootingStar(xPos, yPos) {
    var shootingStar = {x: xPos,
                        y: 0, 
                        size: random(7, 20),
                        speed: random(3, 8),
                        move: moveShootingStar,
                        draw: drawShootingStar}
    return shootingStar; 
}

// DRAWING THE SHOOTING STAR
function drawShootingStar() {
    // controls how the glowing portion of the star 
    // grows and shrinks, so it looks like it's flickering
    var osc = 10 + sin(millis()) * 1;

    noStroke();

    // glowing part of the star
    fill(244, 242, 168, 75);
    ellipse(this.x, this.y, this.size + osc, this.size + osc);

    // inner part of the star
    fill(239, 235, 91);
    ellipse(this.x, this.y, this.size, this.size);
    
}

// MOVING THE SHOOTING STAR
function moveShootingStar() {
    this.y += this.speed; 
}

// ADDING SHOOTING STARS
function addShootingStar(xPos, yPos) {
    shootingStarArray.push(makeShootingStar(xPos, yPos));
}

// REMOVING SHOOTING STARS 
function removeShootingStar() {
    // array for keeping the stars
    var keepStar = [];
    // as long as the shooting stars are in bound of frame 1
    // they will be pushed into the array for keeping the stars
    for (var i = 0; i < shootingStarArray.length; i++) {
        if (shootingStarArray[i].y < f1y + f1height + 
            shootingStarArray[i].size) {
            keepStar.push(shootingStarArray[i]);
        }
    }
    shootingStarArray = keepStar;  
}

// ------------- FUNCTIONS & OBJECTS FOR THE RIPPLES IN FRAME 1 --------------

// RIPPLE OBJECT
function makeRipples(xPos, yPos) {
    var makeRipple = {x: xPos,
                      y: yPos,
                      // longer ripples are in the front, shorter ones in the back
                      length: map(yPos, 212, height, 5, 75),
                      // thinner ripples in the back, thicker ones in the front
                      weight: map(yPos, 212, height, 1, 4),
                      // faster ripples in the front, slower ripples in the back
                      speed: map(yPos, 212, height, 0.5, 1),
                      move: moveRipple,
                      draw: drawRipple}
                    return makeRipple; 
}

// MOVING THE RIPPLE
function moveRipple() {
    // x position changes by speed
    this.x += this.speed; 
    // if the ripple leaves the frame, reset x position
    // to the left side of the frame
    if (this.x > width + this.length) {
        this.x === -this.length;
    }
}

// ADDING RIPPLES
// using a tiny probability, add ripples
function addRipple() {
    if (random(0, 1) < 0.025) {
        ripples.push(makeRipples(-75, random(212, height)));
    }
}

// REMOVING RIPPLES
function removeRipple() {
    // an array for ripples to keep
    var keepRipples = [];
    // as long as ripples are within the bounds of frame 1, 
    // keep them in the keepRipples array
    for (var i = 0; i < ripples.length; i++) {
        if (ripples[i].x < f1x + f1width) {
            keepRipples.push(ripples[i]);
        }
    }
    ripples = keepRipples;
}

// DRAWING THE RIPPLE
function drawRipple() {
    strokeWeight(this.weight);
    stroke(255, 255, 255, 75);
    line(this.x, this.y, this.x + this.length, this.y);
}

// ------------- FUNCTIONS & OBJECTS FOR THE STARS IN FRAME 2 -------------

// SUBWAY STAR OBJECT
function makeStar(xPos, yPos) {
    var subwayStar = {x: xPos,
                      y: yPos,
                      radius1: random(5, 10),
                      npoints: 5,
                      speed: random(0.5, 2),
                      move: moveSubwayStar,
                      draw: drawSubwayStar}
                    return subwayStar; 
}

// MOVING THE SUBWAY STARS
function moveSubwayStar() {
    // x position changes by speed
    this.x += this.speed; 
    // if the star leaves the frame, reset x position
    // to the left side of the frame
    if (this.x > width) {
        this.x === f1x + f1width + 10;
    }
}

// ADDING SUBWAY STARS
// using a tiny probability, add subway stars
function addSubwayStar() {
    if (random(0, 1) < 0.02) {
        subwayStarArray.push(makeStar(f1x + f1width + 10, 
                             random(30, 100)));
    }
}

// REMOVING SUBWAY STARS
function removeSubwayStar() {
    // an array for subway stars to keep
    var keepSubwayStar = [];
    // as long as the stars are within the width, 
    // keep them in a separate array
    for (var i = 0; i < subwayStarArray.length; i++) {
        if (subwayStarArray[i].x < width) {
            keepSubwayStar.push(subwayStarArray[i]);
        }
    }
    subwayStar = keepSubwayStar;
}

// DRAWING THE SUBWAY STARS
function drawSubwayStar() {
    fill(242, 242, 158);
    var angle = TWO_PI / this.npoints;
    var halfAngle = angle / 2.0;
    beginShape();
    for (let a = 0; a < TWO_PI; a += angle) {
        var sx = this.x + cos(a) * (this.radius1 - 5);
        var sy = this.y + sin(a) * (this.radius1 - 5);
        vertex(sx, sy);
        sx = this.x + cos(a + halfAngle) * this.radius1;
        sy = this.y + sin(a + halfAngle) * this.radius1;
        vertex(sx, sy);
    }
  endShape(CLOSE);
}



For my final project, I created a speculative cartoon panel. It’s speculative in the sense that the it forces the viewer to use their imagination to weave a story of what might have happened–the possibilities are endless. In this project, I enjoyed exploring my interests in illustration, concept art, and storytelling. To convey this dreamy, mysterious, magical atmosphere, I worked within a range of cool/teal tones, with pink and yellow highlights.

To begin, I created a moodboard of images/animations on Pinterest. Then, I sketched out/digitized my idea using simple shapes on Illustrator. Not only did I enjoy creating the static images, I think programming the interactions made the comic scene much more fun and engaging.

Moodboarding: I drew a lot of inspiration from space/water concept art. What I love about concept art is its ability to inspire your imagination as you create stories in your head about the piece.
My sketch on Illustrator, which helped save time when visually styling the scene.

Angela Lee – Looking Outwards – 12

I am using 1 of my grace days for this late submission.

For my final project proposal, I’m planning to make a short animation illustrating rising water levels of a personified earth. One project that inspired my idea is 🔥👶🔥 by Joona Leppanen. This gif shows a still figure being partially masked by some smoke. I was particularly inspired by the different textures that Leppanen used in her illustration, but also the naturalness of that smoke. Using simple stroke and fill, she created a convincing air-like substance that communicates her message.

🔥👶🔥 by Joona Leppanen.

The second piece I’d like to talk about is a gif by Yukai Du. Using similar colors as Leppanen (which is coincidental), she’s able to capture water ripples in a simple yet elegant way. I am particularly inspired by how she represents depth through decreasing sizes of the eyes and the ripples. Her work has also made me consider putting material objects in the rising sea levels for my animation and how they can contribute to the story.

A gif from Yukai Du’s website: https://www.yukaidu.com/gifs

Angela Lee – Project 12 – Final Project Proposal

(Using a grace day :))

For my final project, I plan on creating a short animation personifying the Earth (as Mother Earth) and, through her, showing an aspect of the climate crisis. For instance, I found the flood map for 2050 particularly sad, so I might show her losing land mass and being surrounded by rising levels of sea water. I want to personify the earth because I think that seeing the emotions on their face will inspire more empathy, and because I personally love playing with character illustrations. I am fascinated by different illustration styles, but for this project, I would love to explore a simple illustration style with an interesting color palette through the aesthetics of my project. I anticipate animating the water level to be a challenge to get it to look natural and convincing.

A sketch of what it might look like. I have to decide where to show the water level (up to her face, or just up to her neck, etc.) I’d like to play with strokes or even turtle graphics to show the water.

Angela Lee – Project 11 – Generative Landscape

sketch

/*
 * Angela Lee
 * Section E
 * ahl2@andrew.cmu.edu
 * Project 11 Generative Landscape
 */

// tallest mountains
var tallMtnDetail = 0.005; // detail in mountains
var tallMtnSpeed = 0.0001; // speed of mountains

// medium mountains
var medMtnDetail = 0.0075;
var medMtnSpeed = 0.0002; 

// beach 
var beachDetail = 0.003;
var beachSpeed = 0.0004;

// array for ripples
var ripples = [];

var yellow, pink; 

function setup() {
    createCanvas(480, 300);
    frameRate(10);

    // boundaries for ripples
    var top = height * 5/8 + 10;
    var bottom = height - 10;

    // first ripples 
    for (var i = 0; i < 10; i++) {
        var rippleX = random(width);
        var rippleY = random(top, bottom);
        ripples[i] = makeRipples(rippleX, rippleY);
    }

    // gradient for the water
    yellow = color(255, 219, 140);
    pink = color(247, 132, 124);
 }

function draw() {
    noStroke();
    background(255, 156, 161);
    makeSun(); // setting sun
    makeTallMtn(); // tallest mountains
    makeMedMtn(); // middle mountains
    //makeWater();
    gradientWater();

    // ripple functions
    updateRipple();
    removeRipple();
    addRipple(); 

    makeBeach();

}
//----------------- FUNCTIONS BELOW THIS LINE -----------------------------

// SETTING SUN
function makeSun() {
    // sun rays
    noStroke();
    fill(255, 161, 135);
    ellipse(width/2, height * 3/8, 275, 275);

    // sun
    stroke(255);
    strokeWeight(1);
    fill(247, 217, 82);
    ellipse(width/2, height * 3/8, 175, 175);
    
}

// TALLEST MOUNTAINS
function makeTallMtn() {
    fill(252, 119, 165);
    strokeWeight(1);
    beginShape();
    vertex(0, height);
    for (var x = 0; x < width; x++) {
        var t = (x * tallMtnDetail) + (millis() * tallMtnSpeed);
        var y = map(noise(t), 0, 1, height / 8 * 2, height / 8 * 4);
        vertex(x, y);
    }
    vertex(width, height);
    endShape();
}

// MEDIUM MOUTAINS
function makeMedMtn() {
    fill(230, 99, 160);
    strokeWeight(1);
    beginShape();
    vertex(0, height);
    for (var x = 0; x < width; x++) {
        var t = (x * medMtnDetail) + (millis() * medMtnSpeed);
        var y = map(noise(t), 0, 1, height / 8 * 3, height / 8 * 5);
        vertex(x, y);
    }
    vertex(width, height);
    endShape();
}

// OCEAN
function gradientWater() {
    noFill();
    for (var y = height * 5/8; y < height; y++) {
        var inter = map(y, height * 5/8, height, 0, 1);
        var c = lerpColor(yellow, pink, inter);
        stroke(c);
        line(0, y, width, y);
    }
}

// BEACH
function makeBeach() {
    fill(252, 195, 182);
    strokeWeight(1);
    beginShape();
    vertex(0, height);
    for (var x = 0; x < width; x++) {
        var t = (x * beachDetail) + (millis() * beachSpeed);
        var y = map(noise(t), 0, 1, height * 7/8, height * 19/20);
        vertex(x, y);
    }
    vertex(width, height);
    endShape();
}

//----------------- RIPPLE OBJ & FUNCTIONS BELOW THIS LINE -----------------------------

// RIPPLE OBJECT
function makeRipples(xPos, yPos) {
    var makeRipple = {x: xPos,
                      y: yPos,
                      // longer ripples are in the front, shorter ones in the back
                      length: map(yPos, height * 5/8, height, 5, 75),
                      // thinner ripples in the back, thicker ones in the front
                      weight: map(yPos, height * 5/8, height, 1, 4),
                      // faster ripples in the front, slower ripples in the back
                      speed: map(yPos, height * 5/8, height, 1, 4),
                      move: moveRipple,
                      draw: drawRipple}
    return makeRipple; 
}

// MOVING THE RIPPLE
function moveRipple() {
    // x position changes by speed
    this.x += this.speed; 
    // if the ripple leaves the frame, reset x position
    // to the left side of the frame
    if (this.x > width + this.length) {
        this.x === -this.length;
    }
}

// ADDING RIPPLES
// using a tiny probability, add ripples
function addRipple() {
    if (random(0, 1) < 0.075) {
        ripples.push(makeRipples(-75, random(height * 5/8, height)));
    }
}

// REMOVING RIPPLES
function removeRipple() {
    // an array for ripples to keep
    var keepRipples = [];
    for (var i = 0; i < ripples.length; i++) {
        if (ripples[i].x < width) {
            keepRipples.push(ripples[i]);
        }
    }
    ripples = keepRipples;
}

// UPDATE AND DISPLAY RIPPLE
function updateRipple() {
    for (var i = 0; i < ripples.length; i++) {
        ripples[i].move();
        ripples[i].draw();
    }
}

// DRAWING THE RIPPLE
function drawRipple() {
    strokeWeight(this.weight);
    stroke(255);
    line(this.x, this.y, this.x + this.length, this.y);
}







For my landscape, I was inspired the 80s vaporwave illustration style and color. I created a sunset scene and had fun with showing depth through details. For instance, the sizes and speeds of the mountains and ripples are all dependent on the “distance” they would be away from the viewer (the farther something is, the slower and smaller it is).

I’m using a grace day for this project 🙂

A sketch of my beach landscape.

Angela Lee – Looking Outwards – 11

A video showing Shin’m, Eunsu Kang’s environment and performance installation.

I’d like to focus on the media artist Eunsu Kang, the artist behind the installation Shin’m. Kang studied at Ewha Woman’s University, where she received her BFA and MFA. For her postgraduate studies, Kang received an MA from UCSC and a PhD from the University of Washington.

Shin’m is a hybrid of a performance and installation which explores the relationships between the body and space through spatial drawing of sound. Kang created this piece in collaboration with Diana Garcia-Snyder (a choreographer), Donald Craig, and Bo Choi (costume designer). I loved seeing the environment respond visually and sonically to the participants’ movements. I think it inspired the participant to try different movements to see what kind of environment they could generate. Moreover, though the piece is very technical, the sounds of nature add a different dimension to the environment and create a soothing, beautiful atmosphere.

Angela Lee – Project 10 – Sonic Sketches

Long ago, the four nations lived together in harmony. Then, everything changed when the Fire Nation attacked…

sketch

/*
 * Angela Lee
 * Section E
 * ahl2@andrew.cmu.edu
 * Project 10 Sonic Sketch
*/

// illustrations of the avatar characters
// illustrations created by me :)
var imageLinks = [
    "https://i.imgur.com/b8bcUw6.png",
    "https://i.imgur.com/CNlT1Ed.png",
    "https://i.imgur.com/vmDdgoD.png",
    "https://i.imgur.com/yOvvKhm.png"];

// variables in which images will be loaded into
var katara;
var aang; 
var toph; 
var zuko; 

// variables for sound
var waterSound; 
var airSound;
var earthSound; 
var fireSound;  


// loading the images and sounds
function preload() {
    // call loadImage() and loadSound() for all media files here
    aang = loadImage("https://i.imgur.com/b8bcUw6.png");
    katara = loadImage("https://i.imgur.com/CNlT1Ed.png");
    toph = loadImage("https://i.imgur.com/vmDdgoD.png");
    zuko = loadImage("https://i.imgur.com/yOvvKhm.png");

    waterSound = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/11/water-3.wav");
    airSound = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/11/wind.wav");
    earthSound = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/11/earth.wav");
    fireSound = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/11/fire.wav");
}


function setup() {
    createCanvas(480, 480);
    //======== call the following to use sound =========
    useSound();
}


function soundSetup() { // setup for audio generation
    waterSound.setVolume(1);
    airSound.setVolume(1.25);
    earthSound.setVolume(1);
    fireSound.setVolume(1);
}


function draw() {
    background(200);

    // images of the characters
    image(aang, 0, 0, width/2, height/2);
    image(katara, width/2, 0, width/2, height/2);
    image(toph, 0, height/2, width/2, height/2);
    image(zuko, width/2, height/2, width/2, height/2);
}

function mousePressed() {

    // airbending sounds play if user clicks in aang's space
    if (mouseX >= 0 & mouseX < width/2 && 
        mouseY >= 0 && mouseY < height/2) {
        airSound.play();
    } else { // if user clicks away, sound stops
        airSound.pause();
    }

    // waterbending sounds play if user clicks in katara's space
    /*
    note: the water sound has some silence in it, so if you 
    hear silence even after pressing on katara, it may be 
    the silent part of the file. click again to start from 
    the beginning of that sound file.
    */ 
    if (mouseX >= width/2 & mouseX < width &&
        mouseY >= 0 && mouseY < height/2) {
        waterSound.play();
    } else { // if user clicks away, sound stops
        waterSound.pause(); 
    }

    // earthbending sounds play if user clicks in toph's space
    if (mouseX >= 0 & mouseX < width/2 &&
        mouseY >= height/2 && mouseY < height) {
        earthSound.play();
    } else { // if user clicks away, sound stops
        earthSound.pause();
    }

    // firebending sounds play if user clicks in zuko's space
    if (mouseX >= width/2 & mouseX < width &&
        mouseY >= height/2 && mouseY < height) {
        fireSound.play();
    } else { // if user clicks away, sound stops
        fireSound.pause();
    }
}

Because one of the requirements was to feature at least 4 sounds, I thought of things that came in four and could be represented. There are 4 nations in the world of Avatar, which is a show I love. To introduce people who haven’t watched the show before, I used sound to communicate which nation each major character is from. The sounds of earth, air, water, and fire are all pretty distinct and recognizable, so I think it was successful in communicating those nations. Plus, I had a lot of fun creating the illustrations for the characters.

Angela Lee – Looking Outwards – 10

A video introducing the installation “Apparatum” by panGenerator.
A user situated within the installation space.

“Apparatum” is a sound installation with sound inspired by Bogusław Schaeffer and the aesthetics inspired by Oskar Hansen. The installation in general also draws inspiration from the heritage of the Polish Radio Experimental Studio. The project consists of analog sound generators which are controlled through a digitized sheet music touch pad. I admire the speculative nature of the piece. Because it’s not commercial music that has to appeal to a wide audience, it feels much more thoughtful and edgy, and I am drawn to the process of creating it. I think that the artistic sensibilities manifested in the visual design. The aesthetics complement the sound art without overpowering it, since it has minimal grayscale colors, limited use of textures, and consistent forms. The textures of the sound are also quite interesting, challenging you to think of new ways to weave sounds and tones together.

Angela Lee – Looking Outwards – 09

An image of the data visualization “Halo,” created by Ora Systems to communicate a user’s health status.

For this week’s Looking Outwards post, I’ll be discussing Gretchen Kupferschmid’s Looking Outwards 07 about the project “Halo.” Like Gretchen, I also appreciate that the project adds an artistic element to data visualization since data, especially in the medical field, is often displayed traditionally through graphs or just listed out as numbers. Being aware of your health data is very important so that you can make informed decisions that will affect your body positively, and I see this visualization as a way of 1. summarizing dense quantitative content into an impression (that you can take in within a glimpse) and 2. engaging users so that they will want to learn more about their own health. While a creative visualization like this by itself may not provide all the necessary details for a holistic report, integrating the two creates an experience where checking your own health can become a visual delight. I also agree with Gretchen that showing data in this visual way allows users to more intuitively and efficiently compare large sets of data without having to process all the numbers in their head.

Angela Lee – Project 09 – Portrait

sketch

/*
 * Angela Lee
 * Section E
 * ahl2@andrew.cmu.edu
 * Project 9 Computational Portrait
 */

var underlyingImage;

// loading the image
function preload() {
    var url = "https://i.imgur.com/GsniIVQ.jpg";
    underlyingImage = loadImage(url);
}

function setup() {
    createCanvas(300, 400);
    background(145, 1, 10);
    underlyingImage.loadPixels();
    frameRate(60); // how fast the pixels come up
}

function draw() {
    var px = random(width);
    var py = random(height);
    var ix = constrain(floor(px), 0, width);
    var iy = constrain(floor(py), 0, height);
    var theColorAtLocationXY = underlyingImage.get(ix, iy);

    noStroke();
    fill(theColorAtLocationXY);
    ellipseW = random(1, 8); // original ellipse width
    ellipseH = random(1, 8); // original ellipse height
    keyPressed(); // key pressed determines ellipse width and height

    // drawing the ellipse
    ellipse(px, py, ellipseW, ellipseH);

}

// what happens if the key is pressed
function keyPressed() {
    // ellipse will grow if g is pressed
    if (key == "g") {
        ellipseW = random (5, 15);
        ellipseH = random(5, 15);
    } else if (key == "s") {
    // ellipse will shrink if s is pressed
        ellipseW = random(0.5, 3);
        ellipseH = random(0.5, 3);
    } else if (key == "n") {
    // ellipses will return to original size
        ellipseW = random(1, 8);
        ellipseH = random(1, 8);
    }
    return ellipseW;
    return ellipseH;
}

For this portrait, I started out with ellipses being randomized for a width and height ranging from 1 to 5. However, I got impatient while waiting for the portrait to finish, so I decided to make the ellipse grow when you pressed the key “g.” But since I didn’t like how blurry the portrait got, I gave the user the option of returning the ellipses back to its original size or an even smaller size.

Finished portrait
Original photo

Angela Lee – Looking Outwards – 08

A video of Alexander Chen presenting his work at Eyeo 2017.

For this Looking Outwards, I decided to focus on the designer Alexander Chen (http://chenalexander.com/), who is a creative director at Google Creative Lab. He studied Engineering and Digital Media Design at the University of Pennsylvania and is currently working in Cambridge, Massachusetts. He also is a musician, and much of his work integrates his passion for visual design and music. One of his key projects was MTA.ME, in which he translated the New York City subway map into a string instrument. He has also created visualizations of the first Prelude of Bach’s Cello Suites. I particularly enjoy his Bach’s cello suite visualizations because how sophisticated yet natural looking the motions are. I enjoy how his works are very explorative and experimental in nature. While they may not act as solutions to a defined problem, they suggest new and thoughtful ways of visualizing sound that may influence other people’s work. What I enjoyed about his presentation was his conversational tone–it made him as a designer and his work seem very friendly and approachable. Also, he constantly referred to the work and specific parts of a piece he was talking about, so I didn’t have to speculate but could see it for myself. I also admired the straightforwardness and simplicity of his website. I found it easy to navigate and it allowed the work to shine rather than my focus being on the website.