Final Project: Iceberg

sketchDownload

// ICE VAR
var terraine = [];          // array for ice terraine y coordinates
var noiseParam = 0;         // x value of noise func
var noiseStep = .02;        // how much x increases by
var icesize = noiseStep*2500;

// OBJECT VAR
var washer;
var handwasher;
    var bubble;
var bubs = [];
var waterbottle;
var reusebottle;
var car;
var bus;
var shoponline;
var shoplocal;

var allobj = [];            // holds all objects once they are created
var len;                    // length of allobj array
var n = 0;                  // initial spot in allobj array
var q;
var isMouseClicked = -1; // boolean for click

// AUDIO VAR
var waterdrop;  
var watersplash;      


// OBJECT FUNCTIONS
function makeWasher(cx, cy) {
    washer = { x: cx, 
               y: cy,
               show: drawWasher,
               melt: 4,
               txt: "Use electric washer" }
    return washer;
}

function makeHandwasher(cx, cy) {
    for (var i=0; i<30; i++) {
        bubx = random(-35, 35);
        buby = -random(-.5, 3)*i;
        var bub = makeBubble(bubx, buby);
        bubs.push(bub);
    }
    handwasher = { x: cx, 
                   y: cy,
                   show: drawHandwasher,
                   melt: 1,
                   txt: "Hand-wash clothing"  }
    return handwasher;
}

function makeBubble(bubx, buby) {;
    bubble = {x: bubx,
              y: buby,
              sz: random(2, 6),
              show: drawBubble }
    return bubble;
}

function makeWaterbottle(cx, cy) {
    waterbottle = {x: cx, 
                   y: cy, 
                   show: drawWaterbottle,
                   melt: 3,
                   txt: "Plastic water bottle"}
    return waterbottle;
}

function makeReusebottle(cx, cy) {
    reusebottle = {x: cx, 
                   y: cy, 
                   show: drawReusebottle,
                   melt: 1,
                   txt: "Reusable water bottle"}
    return reusebottle;
}

function makeCar(cx, cy) {
    car = {x: cx, 
           y: cy, 
           show: drawCar,
           melt: 5,
           txt: "Drive a car"}
    return car;
}

function makeBus(cx, cy) {
    bus = {x: cx, 
           y: cy, 
           show: drawBus,
           melt: 1,
           txt: "Take public transportation"}
    return bus;
}

function makeOnline(cx, cy) {
    shoponline = {x: cx, 
           y: cy, 
           show: drawOnline,
           melt: 10,
           txt: "Shop online"}
    return shoponline;
}

function makeLocal(cx, cy) {
    shoplocal = {x: cx, 
           y: cy, 
           show: drawLocal,
           melt: 1,
           txt: "Shop local"}
    return shoplocal;
}


// PRELOAD FOR AUDIO FILES
function preload() {
    waterdrop = loadSound("https://courses.ideate.cmu.edu/15-104/f2021/wp-content/uploads/2021/11/splash.wav");
    watersplash = loadSound("https://courses.ideate.cmu.edu/15-104/f2021/wp-content/uploads/2021/12/bigsplash.mp3");
}

// SETUP
function setup() {
    createCanvas(400, 400);
    strokeWeight(2);
    rectMode(CENTER);
    textAlign(CENTER);
    textFont('Georgia');
    textSize(13);
    useSound();

    // fill terraine array w coordinates for ice:
    for (var i=0; i<icesize; i++) {
        var n = noise(noiseParam);      // n is between 0 and 1
        var value = map(n, 0, 1, height/3, height);     // draws onto canvas nicely
        terraine.push(value);           // value is the y coordinate at x=i
        noiseParam += noiseStep;
    }

    // create objects:
    washer = makeWasher(125, 100);
    handwasher = makeHandwasher(275, 100);
    waterbottle = makeWaterbottle(265, 100);
    reusebottle = makeReusebottle(135, 100);
    car = makeCar(125, 100);
    bus = makeBus(275, 100);
    shoponline = makeOnline(290, 100);
    shoplocal = makeLocal(115, 100);

    allobj = [washer, handwasher, 
              reusebottle, waterbottle, 
              car, bus, 
              shoplocal, shoponline];
    len = allobj.length;
}

// SOUND SETUP
function soundSetup() { // setup for audio file
    waterdrop.setVolume(0.5);
    watersplash.setVolume(0.5);
}


// DRAW
function draw() {

    // blue sky:
    background(100, 175, 240);

    // ice:
    drawIce();

    // it melts:
    meltIce();

    // draw objects:
    if (terraine.length > 1) {
        q = n%(len);        // loops through array by 2s
        allobj[q].show();
        allobj[q+1].show();
    }
}

// WHEN USER CLICKS
function mousePressed() {
    let onObj = checkClick();
    if (onObj == true) {
        isMouseClicked = -isMouseClicked;
        n += 2;                 // loops through array by 2s
        isMouseClicked = -isMouseClicked;
    }

}

function checkClick() {
    if (mouseX>75 & mouseX<175 && mouseY>50 && mouseY<150) {
        meltMore(allobj[q]);
        return true;
    }
    if (mouseX>225 & mouseX<325 && mouseY>50 && mouseY<150) {
        meltMore(allobj[q+1]);
        return true;
    }
}


// DRAW ICE
function drawIce() {
    fill(255);
    stroke(0);
    strokeWeight(1);
    beginShape();
    vertex(-5, height);
    vertex(-5, terraine[0]);
    for (var j=0; j<terraine.length-1; j++) {
        vertex(j*5, terraine[j]);
    }
    vertex((terraine.length-1)*icesize, terraine[terraine.length]);
    vertex((terraine.length-1)*5, height);
    endShape();

    // check if completely melted:
    checkIce();
}

// MELT ICE constantly
function meltIce() {
    // when ice is melted, 'game' over
    if (terraine.length == 1) { return }
    // as ice melts, water drop audio is played
    if (frameCount%250==0) {
        terraine.pop();
        waterdrop.play();
    }
}

// MELT MORE! when environmentally more harmful choice is picked
function meltMore(obj) {
    for (var k=0; k<obj.melt; k++) {
        terraine.pop();
    }
    if (obj.melt == 1) { waterdrop.play() }
    else { watersplash.play() }
}

// CHECK IF COMPLETELY MELTED
function checkIce() {
    if (terraine.length <= 1) {
        rect(width/2, height/2, 350, 350);
        textAlign(CENTER);
        textSize(35);
        fill(0);
        text("Oh No!", width/2, 150);
        textSize(16);
        fill(150, 0, 0);
        text("Looks like your choices caused global warming.", width/2, 175);
        textSize(20);
        fill(0);
        text("Next time, try to make better choices.", width/2, 215);
        textSize(13);
        text("You were already trying to make good choices?", width/2, 300);
        text("You picked the best options that were avaiable to you?", width/2, 325);
        textSize(18);
        fill(150, 0, 0);
        text("Have better options next time I guess.", width/2, 350);
        textSize(11);
        noStroke();
        fill(255);
        text("Refresh this page for another try!", width/2, 390);
        noLoop();
    }
}

// DRAW OBJECTS:
// washer
function drawWasher() {
    push();
    translate(this.x, this.y);

    fill(220);
    square(0, 0, 100, 5);

    fill(150);
    circle(0, 0, 75);

    // water in washer:
    noStroke();
    fill(75, 125, 225);
    beginShape();
    vertex(-30, 0);
    vertex(-30, 0);
    curveVertex(-24, 2);
    curveVertex(-18, 0);
    curveVertex(-12, 2);
    curveVertex(-6, 0);
    curveVertex(0, 2);
    curveVertex(6, 0);
    curveVertex(12, 2);
    curveVertex(18, 0);
    curveVertex(24, 2);
    vertex(30, 0);
    curveVertex(26, 16);
    curveVertex(15, 26);
    curveVertex(0, 30);
    curveVertex(-15, 26);
    curveVertex(-26, 16);
    vertex(-30, 0);
    vertex(-30, 0);
    endShape();

    // door partially see-through:
    stroke(0);
    fill(175, 200, 255, 175);
    circle(0, 0, 60);

    // glare on glass door
    noStroke();
    push();
    rotate(radians(40));
    fill(200, 215, 255);
    ellipse(0, -15, 30, 5);
    ellipse(0, -20, 15, 3);
    pop();

    // bottons + stuff

    fill(200, 0, 0);
    circle(40, -40, 3);
    fill(75);
    circle(35, -40, 3);
    circle(30, -40, 3);
    fill(150);
    circle(35, -33, 7);

    // text
    fill(0);
    text(this.txt, 0, 62);

    pop();
}

// handwash basin
function drawHandwasher() {
    push();
    translate(this.x, this.y);

    // basin
    fill(200);
    quad(-50, 0, 50, 0, 35, 40, -35, 40);
    arc(0, 0, 100, 25, 0, PI);
    arc(0, 0, 100, 15, PI, 2*PI);

    // clothes
    noStroke();
    fill(58, 101, 74);
    quad(-30, -7, -15, -8, -15, 10, -32, 8);
    fill(200, 100, 150);
    quad(-20, -8, -7, -9, -5, 10, -25, 10);

    // water
    fill(100, 140, 240);
    beginShape();
    vertex(-45, 6);
    vertex(-45, 5);
    curveVertex(-36, 5);
    curveVertex(-27, 6);
    curveVertex(-18, 5);
    curveVertex(-9, 6);
    curveVertex(0, 5);
    curveVertex(9, 6);
    curveVertex(18, 5);
    curveVertex(27, 6);
    curveVertex(36, 5);
    vertex(45, 5);
    curveVertex(29, 10);
    curveVertex(0, 12);
    curveVertex(-29, 10);
    vertex(-45, 5);
    vertex(-45, 6);
    endShape();

    // suds + bubbles
    fill(200, 215, 255);
    stroke(100, 140, 240);
    strokeWeight(.25);
    for (var i=0; i<30; i++) {
        bubs[i].show()
    }

    // text
    fill(0);
    strokeWeight(1);
    text(this.txt, 0, 62);
    pop();
}

// bubble
function drawBubble() {
    circle(this.x, this.y, this.sz);
}

// plastic bottle
function drawWaterbottle() {
    // cap
    stroke(0);
    strokeWeight(1);
    fill(240);
    rect(this.x, this.y-45, 15, 10);

    // body of bottle
    fill(225, 230, 255);
    arc(this.x, this.y-18, 42, 47, radians(290), radians(250), OPEN);
    beginShape();
    vertex(this.x+21, this.y-15);
    vertex(this.x+21, this.y-15);
    curveVertex(this.x+20, this.y+6);
    curveVertex(this.x+23, this.y+30);
    curveVertex(this.x+19, this.y+50);
    curveVertex(this.x, this.y+50);
    curveVertex(this.x-19, this.y+50);
    curveVertex(this.x-23, this.y+30);
    curveVertex(this.x-20, this.y+6);
    vertex(this.x-21, this.y-15);
    vertex(this.x-21, this.y-15);
    endShape();

    // lines on cap
    stroke(190);
    for (var l=0; l<14; l+=2) {
        line(this.x-6 + l, this.y-41, this.x-6 + l, this.y - 47);
    }

    // wrapper
    fill(75, 125, 200);
    noStroke();
    beginShape();
    vertex(this.x+21, this.y-15);
    vertex(this.x+21, this.y-15);
    vertex(this.x+20, this.y-2);
    vertex(this.x+20, this.y+8);
    vertex(this.x+22, this.y+20);
    vertex(this.x-22, this.y+20);
    vertex(this.x-20, this.y+8);
    vertex(this.x-20, this.y-2);
    vertex(this.x-21, this.y-15);
    vertex(this.x-21, this.y-15);
    endShape();

    // details
    stroke(190, 210, 250);
    noFill();
    arc(this.x, this.y-18, 30, 47, radians(-65), radians(-10));
    arc(this.x, this.y-18, 10, 47, radians(-80), radians(-20));
    arc(this.x, this.y-18, 10, 47, radians(200), radians(260));
    arc(this.x, this.y-18, 30, 47, radians(190), radians(245));
    arc(this.x, this.y+23, 70, 9, radians(10), radians(170));
    arc(this.x, this.y+30, 60, 10, radians(10), radians(170));
    arc(this.x, this.y+37, 55, 11, radians(10), radians(170));

    //wrapper details
    strokeWeight(2);
    stroke(90, 145, 220);
    beginShape();  
    vertex(this.x+15, this.y+10);
    vertex(this.x+15, this.y+10);
    curveVertex(this.x+10, this.y);
    curveVertex(this.x-8, this.y-5);
    vertex(this.x-15, this.y-10);
    vertex(this.x-15, this.y-10);
    endShape();
    beginShape();  
    vertex(this.x+15, this.y+10);
    vertex(this.x+15, this.y+10);
    curveVertex(this.x+8, this.y+5);
    curveVertex(this.x-10, this.y);
    vertex(this.x-15, this.y-10);
    vertex(this.x-15, this.y-10);
    endShape();

    // text
    fill(0);
    strokeWeight(1);
    text(this.txt, this.x, this.y+62);
}

// reusable bottle
function drawReusebottle() {
    stroke(0);
    strokeWeight(1);   

    // water
    fill(100, 155, 230);
    stroke(90, 145, 220);
    beginShape();
    vertex(this.x-23, this.y-18);
    vertex(this.x-23, this.y-18);
    curveVertex(this.x-17, this.y-19);
    curveVertex(this.x-10, this.y-18);
    curveVertex(this.x, this.y-19);
    curveVertex(this.x+10, this.y-18);
    curveVertex(this.x+17, this.y-19);
    vertex(this.x+23, this.y-18);
    vertex(this.x+23, this.y-18);
    endShape();
    noStroke();
    beginShape();
    vertex(this.x+23, this.y-18);
    vertex(this.x+23, this.y-18);
    curveVertex(this.x+24, this.y+30);
    curveVertex(this.x+19, this.y+50);
    curveVertex(this.x-19, this.y+50);
    curveVertex(this.x-24, this.y+30);
    vertex(this.x-23, this.y-18);
    vertex(this.x-23, this.y-18);
    endShape();

    // body of bottle
    noStroke();
    fill(200, 100, 150, 100);
    arc(this.x, this.y-18, 46, 45, radians(185), radians(-5), OPEN);
    noFill();
    stroke(0);
    arc(this.x, this.y-18, 46, 45, radians(290), radians(0), OPEN);
    arc(this.x, this.y-18, 46, 45, radians(180), radians(250), OPEN);
    fill(200, 100, 150, 100);
    beginShape();
    vertex(this.x+23, this.y-20);
    vertex(this.x+23, this.y-20);
    curveVertex(this.x+24, this.y+30);
    curveVertex(this.x+19, this.y+50);
    curveVertex(this.x-19, this.y+50);
    curveVertex(this.x-24, this.y+30);
    vertex(this.x-23, this.y-20);
    vertex(this.x-23, this.y-20);
    endShape();

    // lines on body of bottle
    stroke(190, 90, 140, 200);
    for (var u=0; u<12; u+=1) {
        line(this.x-15, this.y-15+(u*5), this.x-22, this.y-15+(u*5));
    }

    // stickers!
    // smiley face
    stroke(0);
    strokeWeight(.5);
    fill(250, 225, 0);
    push();
    translate(this.x+16, this.y+20);
    rotate(radians(32));
    arc(0, 0, 20, 20, radians(8), radians(287), OPEN);
    arc(0, 0, 13, 13, radians(10), radians(170));
    strokeWeight(1);
    line(-2, 0, -2, -2);
    line(2, 0, 2, -2);
    pop();

    // flower
    push();
    fill(225, 125, 255);
    translate(this.x+9, this.y+36);
    rotate(radians(-20));
    let rot = 0;
    for (var r=0; r<7; r++) {
        push();
        rotate(radians(rot));
        ellipse(6, 0, 8, 4);
        pop();
        rot += 360/7;
    }
    fill(175, 200, 100);
    circle(0, 0, 5);
    pop();

    // cap
    noFill();
    strokeWeight(2);
    stroke(200);
    arc(this.x+14, this.y-50, 29, 25, radians(200), radians(90));
    stroke(0)
    strokeWeight(1);
    arc(this.x+14, this.y-50, 28, 23, radians(200), radians(90));
    arc(this.x+14, this.y-50, 33, 27, radians(200), radians(90));
    fill(200);
    rect(this.x, this.y-52, 15, 5, 2);
    rect(this.x, this.y-45, 25, 15, 2);
    stroke(225);
    strokeWeight(6);
    line(this.x, this.y-40, this.x, this.y-45);
    line(this.x-8, this.y-40, this.x-8, this.y-45);
    line(this.x+8, this.y-40, this.x+8, this.y-45);
    stroke(0);
    strokeWeight(1);
    rect(this.x, this.y-38, 28, 2, 3);

    // text
    fill(0);
    text(this.txt, this.x, this.y+62);
}

// car
function drawCar() {
    let carcol = color(150, 0, 0);
    let detailcol = color(100, 0, 0);

    // exhaust pipe
    fill(120);
    noStroke();
    rect(this.x+60, this.y+27, 5, 3);

    // body of car
    stroke(detailcol);
    fill(carcol);
    beginShape();
    vertex(this.x-40, this.y);
    vertex(this.x-40, this.y+1);
    curveVertex(this.x-20, this.y-22);
    curveVertex(this.x+25, this.y-22);
    curveVertex(this.x+48, this.y);
    curveVertex(this.x+57, this.y+3);
    curveVertex(this.x+58, this.y+10);
    curveVertex(this.x+59, this.y+25);
    curveVertex(this.x+45, this.y+30);
    curveVertex(this.x-60, this.y+30);
    curveVertex(this.x-65, this.y+8);
    vertex(this.x-40, this.y+1);
    vertex(this.x-40, this.y+1);
    endShape();

    // windows
    stroke(150, 160, 200);
    fill(150, 160, 200);
    beginShape();
    vertex(this.x-35, this.y+1);
    vertex(this.x-35, this.y+1);
    curveVertex(this.x-15, this.y-19);
    curveVertex(this.x+20, this.y-19);
    curveVertex(this.x+40, this.y-1);
    curveVertex(this.x+30, this.y+1);
    vertex(this.x-35, this.y+1);
    vertex(this.x-35, this.y+1);
    endShape();
    fill(carcol);
    stroke(carcol);
    strokeWeight(4);
    line(this.x+2, this.y+2, this.x+2, this.y-22);
    strokeWeight(2);
    line(this.x+33, this.y+2, this.x+33, this.y-16);

    // lights
    noStroke();
    fill(230, 230, 175);
    push();
    translate(this.x-62, this.y+9);
    rotate(radians(-35));
    ellipse(0, 0, 10, 7);
    ellipse(-7, 1, 3, 3);
    pop();
    rect(this.x+57, this.y+21, 5, 7, 2);
    rect(this.x+57, this.y+15, 2, 4, 2);

    // handles 
    stroke(detailcol);
    line(this.x-5, this.y+7, this.x, this.y+7);
    line(this.x+4, this.y+7, this.x+9, this.y+7);

    // wheels
    fill(25);
    stroke(0);
    ellipse(this.x+35, this.y+30, 25, 25);
    ellipse(this.x-40, this.y+30, 25, 25);
    let wrot = 0;
    for (var j=0; j<5; j++) {
        push();
        translate(this.x+35, this.y+30);
        rotate(radians(wrot));
        line(-10, 0, 10, 0);
        pop();
        push();
        translate(this.x-40, this.y+30);
        rotate(radians(wrot));
        line(-10, 0, 10, 0);
        pop();
        wrot+=360/10;
    }

    // text
    fill(0);
    strokeWeight(1);
    text(this.txt, this.x, this.y+62);
}

// bus
function drawBus() {
    let buscol = color(50, 75, 255);
    let busdetails = color(20, 40, 200);

    // exhaust pipe
    fill(120);
    noStroke();
    rect(this.x+50, this.y-27, 3, 5);

    // bus body
    fill(buscol);
    stroke(busdetails);
    strokeWeight(2);
    rect(this.x, this.y, 120, 50, 2);

    //details
    line(this.x-45, this.y+8, this.x+55, this.y+8);
    strokeWeight(1);
    line(this.x-45, this.y+10, this.x+55, this.y+10);

    // windows
    stroke(150, 160, 200);
    strokeWeight(2);
    fill(150, 160, 200);
    rect(this.x-57, this.y-5, 5, 25);
    for (var r=0; r<10; r++) {
        rect(this.x-41+(10*r), this.y-3, 7, 15);
    }

    // wheels
    fill(25);
    stroke(0);
    ellipse(this.x+38, this.y+30, 15, 15);
    ellipse(this.x+22, this.y+30, 15, 15);
    ellipse(this.x-45, this.y+30, 15, 15);
    ellipse(this.x-29, this.y+30, 15, 15);
    strokeWeight(1);
    let wrot = 0;
    for (var j=0; j<5; j++) {
        push();
        translate(this.x+38, this.y+30);
        rotate(radians(wrot));
        line(-7, 0, 7, 0);
        pop();
        push();
        translate(this.x+22, this.y+30);
        rotate(radians(wrot));
        line(-7, 0, 7, 0);
        pop();
        push();
        translate(this.x-45, this.y+30);
        rotate(radians(wrot));
        line(-7, 0, 7, 0);
        pop();
        push();
        translate(this.x-29, this.y+30);
        rotate(radians(wrot));
        line(-7, 0, 7, 0);
        pop();
        wrot+=360/10;
    }

    // lights
    noStroke();
    fill(230, 230, 175);
    ellipse(this.x-58, this.y+15, 7, 10);
    rect(this.x-59, this.y+15, 5, 10, 3);
    rect(this.x+57, this.y+21, 5, 7, 2);

    // text
    fill(0);
    strokeWeight(1);
    text(this.txt, this.x, this.y+62);
}

// plane
function drawOnline() {
    fill(200);
    stroke(0);

    push();
    translate(this.x, this.y)
    //back wing
    arc(-9, 10, 150, 16, radians(90), radians(270));
    push();
    translate(-25, 13);
    rotate(radians(31));   
    arc(-6, 0, 40, 14, radians(270), radians(90));
    circle(-6, 0, 14);
    pop();
    //small tail wing back
    arc(55, 35, 45, 10, radians(90), radians(270));
    push();
    rotate(radians(31));
    //body
    arc(10, 0, 150, 40, radians(175), radians(357));
    rotate(radians(-6));
    arc(10, 0, 150, 40, radians(4), radians(185));
    noStroke();
    ellipse(-60, -3, 10, 14);
    stroke(0);
    line(-65, -1, -65, -6);
    rotate(radians(6));
    // windows
    strokeWeight(.5);
    fill(150, 160, 200);
    quad(-61, -6, -65, -1, -40, -1, -40, -6);
    for (var r=0; r<10; r++) {
        ellipse((10*r)-30, -7, 6, 7);
    }
    pop();
    //back fin thingy
    stroke(0);
    fill(200);
    push();
    translate(52, 13);
    rotate(radians(22));
    beginShape();
    vertex(0, 0);
    vertex(0, 0);
    curveVertex(15, -3);
    vertex(30, -15);
    vertex(30, 16);
    vertex(30, 16);
    endShape();
    pop();
    //small tail wing front
    arc(70, 35, 45, 10, radians(260), radians(100));
    //front wing
    arc(12, 8, 175, 20, radians(220), radians(0));
    arc(12, 8, 175, 15, radians(0), radians(50));
    push();
    translate(25, 13);
    rotate(radians(31));  
    arc(-10, 0, 40, 14, radians(270), radians(90));
    circle(-10, 0, 14);
    pop();  

    // text
    fill(0);
    strokeWeight(1);
    text(this.txt, 0, 62);    
  
    pop();
}

// store
function drawLocal() {
    square(this.x, this.y, 100);
    noFill();
    push();
    translate(this.x, this.y);

    // building
    fill(150, 170, 150);
    stroke(0);
    rect(0, 15, 100, 70);
    stroke(125, 145, 125);
    for(var l=0; l<11; l++) {
        line(-49, -15+(l*6), 49, -15+(l*6));
    }

    //roof
    stroke(0);
    fill(150, 100, 50);
    quad(-60, -20, 60, -20, 50, -50, -50, -50);

    //door
    stroke(15, 75, 15);
    fill(15, 75, 15);
    rect(0, 37, 16, 25);
    arc(0, 25, 16, 30, radians(180), radians(0));
    stroke(0);
    line(7, 30, 2, 30);


    //windows
    strokeWeight(.5);
    fill(150, 160, 200);
    rect(-30, 15, 30, 40);
    rect(30, 15, 30, 40);
    for (var m=0; m<3; m++) {
        line(-38+(m*8), 35, -38+(m*8), -5);
        line(38-(m*8), 35, 38-(m*8), -5);
        line(-45, 5+(m*10), -15, 5+(m*10));
        line(45, 5+(m*10), 15, 5+(m*10));
    }

    //signs in windows
    fill(180);
    line(15, 15, 30, 25);
    line(45, 15, 30, 25);
    rect(30, 25, 15, 8);
    line(-15, 15, -30, 25);
    line(-45, 15, -30, 25);
    rect(-30, 25, 15, 8);
    noStroke()
    fill(150, 0, 0);
    textAlign(CENTER);
    textSize(4);
    text('OPEN!', -30, 26);
    fill(0);
    textSize(3);
    text('Hours:', 28, 25);
    stroke(0);
    line(26, 26, 35, 26);
    line(26, 27, 35, 27);
    fill(100);

    // text
    fill(0);
    textSize(13);
    strokeWeight(1);
    text(this.txt, 0, 62);

    pop();
}


My program depicts an iceberg that is melting, and presents the player with common choices that people have to make on a regular basis. The canvas reacts to the decisions that the player makes. The choices all have varying environmental impacts, and they correspond to the melting of the iceberg. If the player picks the environmentally more harmful choice, more of the ice falls into the ocean. If the player chooses an environmentally friendly option, the ice continues to melt at the same rate. This interactive program is not a winnable game. The ice will always melt. As it melts, a splash sound is played. **

I chose this design as my project because I wanted to highlight the way that industries pressure individuals to make good personal choices while producing products that only cause more damage. Individual choices will not stop climate change. To help preserve our planet, we need to rethink the entire system of the global enocomy.

The program must use a local server to run, and two sound files should be included in the folder to make the splashing noises when the ice melts. There are instructions included on the opening page.

I am really happy with how this project turned out. I only have a few choice that are presented to the player, but my goal was to make at least four sets of options, and that is what I have done. I screen recorded a majority of my process, and I have included a time-lapsed video at the bottom of this post.

** for the WordPress upload, my splash sound file was not working so I used a substitute file with a similar sound.

Time lapse of my process

Project 11: Generative Landscape

sketchDownload
// this program displays a landscape of a stone path along a stream in the woods.

// empty arrays for objects:
var bkgrndtrees = [];
var path = [];
var stream = [];
var trees = [];
var allArrays = [bkgrndtrees, path, stream, trees];

// speed of landscape shift, per frame
// adjusted for background+foreground so 'closer' objects move faster than 'far away' ones
var shift = -1;

function setup() {
    createCanvas(480, 300);
    background(80, 125, 60);
    frameRate(200);

    // create initial collections of objects:
    fillBkgrndtrees();
    fillPath();
    fillStream();
    fillTrees();

    // sort objects in arrays to be drawn properly:
    for (a=0; a<allArrays.length; a++) {
        if (allArrays[a]!=stream){          // stream should not be ordered
            orderObjects(allArrays[a]);
        }
    }
}

// fills backgroundTrees array with initial trees:
function fillBkgrndtrees() {
    for (var i = 0; i < 320; i++) {
        var rx = random(width+75);
        var ry = random(-height/25, 2*height/5);      // top of canvas = background
        bkgrndtrees[i] = makeTree(rx, ry);
    }
}

// fills foreground tree array with initial trees:
function fillTrees() {
    for (var i = 0; i < 5; i++) {
        var rx = random(width+75);
        var ry = random(4*height/5, 3*height/2);    // bottom of canvas = foreground
        trees[i] = makeTree(rx, ry);
    }
}

// fills path array with initial stones:
function fillPath() {
    for (var i = 0; i < 15; i++) {
        var size = random(width/25, width/10);
        var col = color(random(90, 120));
        var x = i*50;
        var ry = random(height/2, 3*height/5);
        path[i] = makeStone(x, ry, size, col);
    }
}

// fills stream array with objects based on stone path:
function fillStream() {
    for (var i = 0; i < path.length; i++) {
        var x = i*50;
        var y = path[i].y + path[i].size;
        stream[i] = makePoint(x, y);
    }
}

// creates a tree object
function makeTree(tx, ty) {
    // background trees:
    if (ty < height/2) {
        var tree = {x:tx,
                    y:ty,
                    age:random(2, 11),
                    trunkc: color(random(70, 90), random(55, 75), random(30, 55)),
                    leavesc: color(random(0, 75), random(65, 175), random(50)),
                    show: showTree,
                    move: moveObject,
                    speed: shift*.85 }
    }
    // foreground trees:
    else {
        var tree = {x:tx,
                    y:ty,
                    age:random(4, 7),
                    trunkc: color(random(50, 80), random(20, 50), random(10, 20)),
                    leavesc: color(random(0, 100), random(100, 200), random(30)),
                    show: showTree,
                    move: moveObject,
                    speed: shift*1.15 }
    }

    return tree;
}

// creates a stone object
function makeStone(sx, sy, ssize, scol) {
    var stone = {x:sx,
                 y:sy,
                 size:ssize,
                 c: scol,
                 show: showStone,
                 move: moveObject,
                 speed: shift}
    return stone;
}

// creates a point object (for stream curve);
function makePoint(px, py) {
    var pnt = {x:px,
               y:py,
               size:width/50,
               c: color(50, 125, 175),
               show: showStone,
               move: moveObject,
               speed: shift}
    return pnt;
}

function draw() {
    background(80, 125, 60);

    // draw all objects in all arrays:
    for (j=0; j<allArrays.length; j++) {
        thisArray = allArrays[j];
        drawObjects(thisArray);
        if (thisArray!=stream) {        // stream is updated with path
            // order and updates arrays:
            orderObjects(thisArray);
            updateArray(thisArray);
        }
    }
}

// draws and moves objects in an array
function drawObjects(array) {
    for (i=0; i<array.length; i++) {
        thisObj = array[i];
        thisObj.show();
        thisObj.move();
    }
    if (array==stream) {
        drawStream();
    }
}

// uses curve shape and array points ot draw stream
function drawStream() {
    push();
    noFill();
    stroke(50, 125, 175);
    strokeWeight(15);
    curveTightness(-.2);
    beginShape();
    curveVertex(stream[0].x, stream[0].y);
    curveVertex(stream[0].x, stream[0].y);
    for (var m=1; m<stream.length-1; m++){
        curveVertex(stream[m].x, stream[m].y);
    }
    curveVertex(stream[stream.length-1].x, stream[stream.length-1].y);
    endShape();
    pop();
}

// sorts objects in an array according to the y feild
// this ordering ensures accurate depth on canvas
function orderObjects(array) {
    for (var j=0; j<array.length-1; j++) {
        var counter = 0;
        for (var i=0; i<array.length-1; i++) {
            if (array[i].y > array[i+1].y) {
                var tmp = array[i];
                array[i] = array[i+1];
                array[i+1] = tmp;
                counter += 1;
            }
        }
        if (counter==0) {
            break;
        }
    }
}

// updates all allArrays
// adds new objects to end and deletes unused objects (off canvas)
function updateArray(array) {
    if (array==trees || array==bkgrndtrees) {
        // add new trees to array off-canvas:
        // background trees:
        var treeLikelihoodB = 320/555;
        if (random(1) < treeLikelihoodB) {
            var ry = random(-height/25, 2*height/5);    // top of canvas = background
            bkgrndtrees.push(makeTree(width+75, ry));
        }
        // foreground trees:
        var treeLikelihoodF = 5/555;
        if (random(1) < treeLikelihoodF) {
            var ry = random(3*height/4, 3*height/2);      // bottom of canvas = foreground
            trees.push(makeTree(width+75, ry));
        }
    }
    else {
        // path and stream use fixed x values:
        if (frameCount%50==0) {
            var size = random(width/25, width/10);
            var col = color(random(90, 120));
            var ry = random(height/2, 3*height/5);
            path.push(makeStone(700, ry, size, col));
            col = color(0, 50, 150);
                var y = ry + size;
            stream.push(makePoint(700, y));
        }
    }

    // remove any object no longer on canvas:
    var keep = [];
    for (var k=0; k<array.length; k++) {
        if (array[k].x > 0) {
            keep.push(array[k]);
        }
    }
    array = keep;
}

// draws tree based on age
function showTree() {
    if (this.y < height/2) {     // top of canvas = background
        var h = this.age*10;
        var w = this.age*2;
    }
    else {                      // bottom of canvas = foreground
        var h = this.age*12;
        var w = this.age*2.5;
    }
    var cx = this.x + w/2;
    var cy = this.y - h*1.5;

    if (this.age > 5) {             // older trees
        stroke(50, 40, 30);
        fill(this.trunkc);
        rect(this.x, this.y-h, w, h);
        fill(this.leavesc);
        for (var i=0; i<3; i++) {
            stroke(0, 75, 0);
            circle(cx - (this.age*4*i) + (this.age*4),
                   cy - (this.age*1.5) + (this.age*3*i),
                   this.age*5);
            circle(cx + (this.age*4*i) - (this.age*4),
                   cy - (this.age*1.5) + (this.age*3*i),
                   this.age*5);
            ellipse(cx - (this.age*6*i) + (this.age*6),
                    cy + this.age,
                    this.age*5,
                    this.age*6);
            ellipse(cx,
                    cy - (this.age*3) + (this.age*4*i),
                    this.age*7,
                    this.age*5);
        }
        noStroke();
        ellipse(cx, cy + this.age, this.age*12, this.age*9);
    }
    else {                          // younger trees
        stroke(50, 40, 30);
        fill(this.trunkc);   // tree trunk lighter brown
        rect(this.x, this.y-h, w/2, h);
        fill(this.leavesc);
        for (var i=0; i<3; i++) {
            stroke(0, 75, 0);
            circle(cx - (this.age*3*i) + (this.age*3),
                   cy - (this.age*1.5) + (this.age*2*i),
                   this.age*4);
            circle(cx + (this.age*3*i) - (this.age*3),
                   cy - (this.age*1.5) + (this.age*2*i),
                   this.age*4);
            ellipse(cx - (this.age*4*i) + (this.age*4),
                    cy + this.age,
                    this.age*4,
                    this.age*5);
            ellipse(cx,
                    cy - (this.age*4) + (this.age*4*i),
                    this.age*6,
                    this.age*4);
        }
        noStroke();
        ellipse(cx, cy + this.age/2, this.age*11, this.age*9);
    }
}

// draws stone for path
function showStone() {
    stroke(75);
    fill(this.c);
    ellipse(this.x, this.y, this.size, this.size/2);
}

// updates x values to move the landscape along
function moveObject() {
    this.x += this.speed;
}

For this project, I was inspired by a trail I used to walk a lot growing up. I struggled a bit to order the objects in a way that looks nice and is recognizable as a landscape, but I think the orderObjects() function that I created helped a lot with that. I also found it difficult to create the stream using a curve shape, but I am again really pleased with how well it came out. I wanted the path and stream to flow along together, and they do. To give more of a landscape effect, I adjusted the shift values for moving the objects so that things in the foreground move faster than things in the background.

My initial sketch of the stream in the woods.

Looking Outwards 11: Societal Impacts of Digital Art

The reading I chose for this blog post is ‘Women in Media Arts: Does AI think like a (white) man?’ The article focuses on female digital artists and feminism within the digital art realm. Through looking at a number of female-created art pieces and projects, the article discusses the ways in which AI and other creative practices shed light on the biases within creators and consumers. One project by Mary Flanagan entitled [“help me know the truth”] explores the way people reinforce their own biases based on others’ physical appearance. Other projects in this article were created with the purpose of combatting the obvious marginalization and discrimination that result from the lack of diversity among those creating digital tools. One example of this is facial recognition software which generally does not accurately recognize people of color. This is just one of the many ways human bias influences artificial intelligence, and there are very real and dangerous consequences that can arise if these types of creative habits are not broken (ex: medical equipment that is only accurate for white male patients).

article link

4. Project 10: Sonic Story

sketch – CopyDownload
// This program displays a game of cup pong.
// The 'characters' are the ball, cups, table, and water in the cups.
// When the ball bounces on different surfaces (on the floor, into a cup, etc.),
// it produces different sounds.
// One side wins when all of the cups are empty
//(or just a few if you change the 'win' variable in the draw function).

// sounds:
var watercup;
var emptycup;
var bouncefloor;
var throwing;
var gameover;


// objects:
var cup; // will have fields for cx, cy, and boolean isFull
var ball; // will have fields for x, y, dx, dy, and functions

// arrays:
var leftCups = [];
var rightCups = [];

function preload() {
    watercup = loadSound("https://courses.ideate.cmu.edu/15-104/f2021/wp-content/uploads/2021/11/pong-sounds-with-liquid.wav");
    bouncefloor = loadSound("https://courses.ideate.cmu.edu/15-104/f2021/wp-content/uploads/2021/11/bouncing-ping-pong-ball.wav");
    throwing = loadSound("https://courses.ideate.cmu.edu/15-104/f2021/wp-content/uploads/2021/11/throwing-paper-in-trash.wav");
    gameover = loadSound("https://courses.ideate.cmu.edu/15-104/f2021/wp-content/uploads/2021/11/ending.wav");
}

// makes a cup object, always full to begin
function makeCup(cx, cy) {
    cup = {x:cx, y:cy, isFull: true, drawc: drawCup};
    return(cup);
}

// makes a ball object
function makeBall(cx, cy, dirx, diry) {
    ball = {x:cx, y:cy,
            dx:dirx, dy:diry,
            drawb: drawBall,
            throwb: throwBall,
            resetb: resetBall,
            leftright: true};
    return(ball);
}

function setup() {
    createCanvas(480, 300);
    frameRate(35);	// high framerate is better for this program
    useSound();
    rectMode(CENTER);

    // fill cup arrays:
    // set x and y positions to create pyramid shape.
    // the if statements create the rows for the pyramid
    for (var i=0; i<10; i++) {
        if (i<4) {
	    lcup = makeCup(width/10+10,	(i*20)+120);
            rcup = makeCup(9*width/10-10, (i*20)+120);
        }
        else if (i<7) {
	    lcup = makeCup(width/10+30,	(i*20)+50);
            rcup = makeCup(9*width/10-30, (i*20)+50);
        }
        else if (i<9) {
	    lcup = makeCup(width/10+50,	i*20);
            rcup = makeCup(9*width/10-50, i*20);
        }
        else {
	    lcup = makeCup(width/10+70,	height/2);
            rcup = makeCup(9*width/10-70, height/2);
        }
        leftCups.push(lcup);
        rightCups.push(rcup);
    }

    // create ping pong ball
    let dy = random(-4, 4);
    ball = makeBall(5, height/2, 5, dy);
}

function soundSetup() { // setup for audio generation
    watercup.setVolume(0.5);
    bouncefloor.setVolume(0.5);
    throwing.setVolume(0.5);
    gameover.setVolume(0.5);
}


function draw() {
    background(133, 94, 66);

    // draw the table
    drawTable();

    // draw the cups
    for (var c=0; c<leftCups.length; c++) {
        leftCups[c].drawc();
        rightCups[c].drawc();
    }

    // draw the ball
    ball.drawb();

    // throw the ball
    ball.throwb();

    // check for winner
    let checkl = 0;
    let checkr = 0;
    for (l=0; l<leftCups.length; l++) {
        if (leftCups[l].isFull == false) { checkl+=1 }
    }
    for (r=0; r<rightCups.length; r++) {
        if (rightCups[r].isFull == false) { checkr+=1 }
    }
    // when one side wins, display text and play sound
    // change 'win' value to see game over sooner:
    let win = 10;
    if (checkl == win|| checkr == win) {
        gameover.play();
        textAlign(CENTER, CENTER);
        textSize(25);
        fill(0);
        rect(width/2, height/2, width/2, 50, 10);
        fill(255);
        text('Game Finished!', width/2, height/2);
        noLoop();
    }


}

// draws a green table with offwhite diagonal lines:
function drawTable() {
    let linescol = color(250, 250, 225);
    let tcol = color(0, 100, 100);
    strokeWeight(7);
    stroke(linescol);
    fill(tcol);
    rect(width/2, height/2, 4*width/5, 4*height/5);
    line(width/10, height/10, 9*width/10, 9*height/10);
    line(width/10, 9*height/10, 9*width/10, height/10);
}

// draws individual cups based on x and y fields:
function drawCup() {
    let diam = 20;
    fill(200, 0, 0);	// red cup
    stroke(255);
    strokeWeight(2);
    circle(this.x, this.y, diam);
    // if the cup hasnt been hit, it has water in it:
    if (this.isFull == true) {
        noStroke();
        fill(0, 0, 200);	// blue water
        circle(this.x, this.y, diam/2);
    }
}

// draws the ball
function drawBall() {
    stroke(0);
    strokeWeight(.5);
    fill(250);
    circle(this.x, this.y, 10);
}

//throws the ball
function throwBall() {
    if (this.leftright==true) {		// going left to right
        if (this.x < width/2) {
            this.x += this.dx;
            this.y -= this.dy;
        } else {
            this.x += this.dx;
            this.y += (1.5*this.dy);
        }
        // remove the water if the ball goes into a full cup and reset ball placement
	// play water cup noise if cup is hit
        for (var j=0; j<rightCups.length; j++) {
            if (dist(rightCups[j].x, rightCups[j].y, this.x, this.y)<=7 &
		rightCups[j].isFull==true) {
	   	watercup.play();
                rightCups[j].isFull = false;
                this.resetb();
            }
        }
    } else {				// going right to left
        if (this.x > width/2) {
            this.x -= this.dx;
            this.y -= this.dy;
        }
        else {
            this.x -= this.dx;
            this.y += (1.5*this.dy);
        }
	// same code to remove water from hit cup
        for (var j=0; j<leftCups.length; j++) {
            if (dist(leftCups[j].x, leftCups[j].y, this.x, this.y)<=7 &
		leftCups[j].isFull==true) {
	   	watercup.play();
                leftCups[j].isFull = false;
                this.resetb();
            }
        }
    }
    // no cups are hit, reset the ball position & play floor bounce track
    if (this.x > width || this.x < 0 ) {
       	this.resetb();
        bouncefloor.play();
    }
}

//resets the ball to the next players starting position
function resetBall() {
    this.leftright = -this.leftright;
    if (this.leftright == true) {this.x=10}
    else {this.x =width-10}
    this.y = height/2;
    this.dy = random(-4, 4);
    // plays a 'whoosh' track for each throw
    throwing.play();
}

I chose to make a program that displays a game of water pong. The sounds I use are a ‘whoosh’ noise when a ball is thrown, a ‘clink’ sound if the ball goes into a full cup, a bouncing pingpong ball noise if the ball does not go into any full cups, and a bell noise if there is a winner. My code is randomized in a way that every game will be slightly different.

Project 09: Computational Portrait

sketchDownload
// This program deconstructs an image and then reconstructs it in a new way.
// The image is sliced into pieces and then drawn together in a new order
// Horizontally and vertically

var img;
var lineHeight = [];
var lineWidth = [];
var numSlices = 6;   //must be even

// slices are ordered and grouped into 4 arrays
var imgSlices1 = [];
var imgSlices2 = [];
var imgSlices3 = [];
var imgSlices4 = [];

function preload() {
    img = loadImage('https://i.imgur.com/c6ZRijQ.jpg');
}

function setup() {
    createCanvas(480, 480);
    imageMode(CENTER);
    noStroke();
    img.loadPixels();

    // get x and y values for the slices based on canvas size and number of slices
    for (var i=0; i<numSlices; i++) {
        let h = height/(numSlices+1);
        let h1 = height/numSlices;
        let w = width/(numSlices+1);
        let w1 = width/numSlices;
        lineHeight[i] = h1*i + h1/2;
        lineWidth[i] = w1*i + w1/2;
        // rearrange image pieces based on odd or even and fill arrays
        if (i%2==0) {
          imgSlices2.push(img.get(0, lineHeight[i], width/2, h));
          imgSlices4.push(img.get(lineWidth[i], 0, w, height));
        }
        else {
          imgSlices1.push(img.get(0, lineHeight[i], width/2, h));
          imgSlices3.push(img.get(lineWidth[i], 0, w, height));
        }
    }
}

function draw() {
    //there is some background that shows ecpecially with small numSlices values
    // this sets background to match original image
    var bgrnd = img.get(random(width), random(height));
    background(bgrnd);

    //draw image in new order
    for (var i=0; i<numSlices/2; i++) {
        image(imgSlices1[i], width/4, lineHeight[i]*2);
        image(imgSlices2[i], 3*width/4, lineHeight[i]*2);
        image(imgSlices3[i], lineWidth[i], height/2);
        image(imgSlices4[i], lineWidth[i]+width/2, height/2);
    }
    noLoop();
}

Based on what I’ve seen classmates post, I am not sure I did this project correctly. I am really happy with my outcome, though, and it is close to what I visualized before I started writing any code. When I read the prompt for this assignment, I was inspired by I video I had seen of an artist cutting up pictures and then putting them back together in strange ways that added new perspective and dimension. With this in mind, I decided to cut up the image and then layer it on top of itself in new ways. Technically, the image is displayed twice, but not the same way either time. With a quick change of code you can see how the portrait is drawn with more or fewer ‘slices’.

numSlices = 4
numSlices = 6
numSlices = 10
numSlices = 30

Looking Outwards 09: A Focus on Women and Non-binary Practitioners in Computational Art

The artist I’ve chosen to write about this week is the amazing electronic music and effects composer, Suzanne Ciani. Beginning at the age of 22, Ciani has wom awards throughout her life for her musicianship, creativity, leadership, and contribution to the electronic music industry. She is the subject of a documentary that was filmed in 2014 to highlight her life and uniquely successful music career. As one of very few women in the music technology major here at CMU, I feel very inspired personally by Suzanne Ciani and I look up to her as a role model. Her design and production of the soundtrack to Greg Kmiec’s Xenon pinball machine is considered a significant milestone in game design history. With the use of new technology (in 1980), Ciani created a composition of interesting and catchy music that reacted directly to gameplay. This added a completely new layer to the game by providing a complex sensory experience that interacted directly with the person playing. I love this concept, and I am excited to try and implement this idea into some of my own projects.

Xenon by Suzanne Ciani, 1980

Suzanne’s website

Looking Outwards 08: The Creative Practice of an Individual

The artist I chose to learn about this week is Alexander Chen, a Creative Director at Google Creative Lab who focuses primarily on music visualization. He has contributed to a plethora of tools and gadgets that bring a new perspective to sound. One of his projects, the Spectrogram, is probably the most visually appealing spectrogram widely used on the internet. In his Eyeo Festival presentation, he demonstrated how different types of sounds look on the spectrogram and how harmonies can be easier to conceptualize when the frequencies are mapped out spatially. Other projects of his, like a string instrumental version of the New York City subway system and its real-time schedule, bring together many abstract ideas into one cohesive work. I personally love the simple style of his pieces and the way he chooses to follow curiosity to create unique experiences. As a musician working in production and engineering, it is exciting to see new ways of experiencing music and to think about sound through a brand new lens.

Alexander Chen

MTA.ME transformes a New York subway map into a string instrument

Project 07: Composition with Curves

sketchDownload
// This program displays a grid of Epitrochoid curves with increasing nPoints and cusps.
// The canvas sets up with a 5x5 grid; 
// When the mouse is pressed, more curves are drawn.
// Pressing the spacebar deletes one row and column. 

var c;				// color 
var density = 5;	// number of curves in each row and column
var nPoints;		// number of points used to draw each curve
var cusp;			// number of cusps on each curve
var mode = 0		// default mode uses Epitrochoid formula, other mode uses an edited Epitrochoid-like formula

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

function draw() {
	background(200, 200, 255);
	// draw a grid of curves based on density value:
	for (var k=0; k<density; k++) {
		cusp = k+1;							// number of cusps increases from left to right
		for (var j=0; j<density; j++) {
			push();
			translate(k*width/density+240/density, 	// keeps grid centered regardless of density changes
					  j*height/density+240/density);	
			nPoints = (j+1)*density			// number of nPoints increases from top to bottom
		
        	//color based on grid and mouse location:
			let r = map(j, 0, density, 0, 255);
			let g = map(k, 0, density, 0, 255);
			let b = map(mouseX, 0, width, 0, 255);
			c = color(r, g, b);
			
			// check which mode we are in and draw curves:
			if (mode == 0) {drawEpitrochoidCurve(nPoints, cusp, c)}
			else {drawEpitrochoidyCurve(nPoints, cusp, c)}
			pop();
		}
	}
	// labeling for easier understanding of the grid pattern:
	textFont('monospace');
	text('click mouse for more curves,	press spacebar for fewer curves', 20, height-12);
	text('press "w" for weird curves,	press "r" for default curves', 20, height-3);
	text('n u m b e r		o f		c u s p s   ----- >', 5, 10);
	text('n P o i n t s', 5, 30, 1, 150);
	push(); 
	rotate(radians(90));
	text('----- >', 140, -5);
	pop();
	

}

// code adapted from sample in project description:
function drawEpitrochoidCurve(nPoints, cusp, color) {
    // Epitrochoid:
	//https://mathworld.wolfram.com/Epitrochoid.html
	
    var x;
    var y;

	var a = 15;
    var b = a / cusp;
    var h = map(mouseY/8, 0, height, 0, a*5);
    var ph = -mouseX / 40;
    
    fill(color);

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

// code adapted from sample in project description and edited further:
// these curves do not interact with mouseY:
function drawEpitrochoidyCurve(nPoints, cusp, color) {
    // Epitrochoidy: not quite an Epitrochoid, but follows a very similar formula
	
    var x;
    var y;

	var a = 15;
    var b = a / cusp;
    var ph = -mouseX / 40;
    
    fill(color);

	// shape of curve:
    beginShape();
    for (var i = 0; i < nPoints; i++) {
        var t = map(i, 0, nPoints, 0, TWO_PI);
        
        x = (a + b) * cos(t) * cos(ph + t * (a + b) / b);
        y = (a + b) * sin(t) * sin(ph + t * (a + b) / b);
        vertex(x, y);
    }
    endShape(CLOSE);
}

// add one more row and column each time the mouse is pressed:
function mousePressed() {
	density +=1;
}

//delete a row and column when the SPACEBAR is pressed:
function keyPressed() {
	if (keyCode == 32) { 
		if (density == 0) {density = density }		// dont let density variable go below 0
		else {density -= 1 }
	}
	if (keyCode == 87) { 
		if (mode == 0) {mode = 1}		// pressing 'w' switches to 'weird' mode
	}
	if (keyCode == 82) { 
		if (mode == 1) {mode = 0}		// pressing 'r' switches back to default 'regular' mode
	}
}

After deciding to use the Epitrochoid curve formula, I realized that the sample in our project description is a very similar curve. Using the sample formula, I adapted the code until I liked what was being drawn on the canvas. Then I made a grid of curves and built them using various nPoint values and cusp values. As you can see in the drawing, as the curves are drawn from left to right, there are more cusps; and as they are drawn from top to bottom, there are more nPoints. Similarly to the sample code, my project uses mouseX and mouseY to interact with the angle and width of the curves. Additionally, the mouseX and mouseX variables are used to determine the fill color of each curve. When the mouse is clicked, the grid gets denser; when the spacebar is pressed, the density decreases. After playing around with the formula, I decided that I also wanted to include a mode for Epitrochoid-like curves that are not technically following the Epitrochoid formula. If you press ‘w’, the ‘weird’ mode turns on and the curves look strange and interesting. Pressing the ‘r’ key puts the program back into the default ‘regular’ mode. Below are a few screen shots that document some of my proccess:

Looking Outwards 07: Information Visualization

The project I have chosen for this blog post is Kim Rees’s data visualization, “Revealing the overwhelming magnitude of loss from U.S. gun deaths.” This project used data from the FBI’s Uniform Crime Report to map every individual gun murder victim from a given year at once. I find this type of data visualization to be extremely important because it gives a more realistic perspective to viewers who would not otherwise be able to grasp the magnitude of impact by simply looking at the raw numbers. In this project, Rees includes a visualization of what she labels as “Stolen Years”, or the number of years that each murder victim is estimated to have likely lived had they not been shot. The website allows viewers to use filters with can show the proportion of gun murder victims who fall into a specific category or demographic. For example, using the race and age group filters, I am able to see that 433 black children were killed at the hands of gun violence in 2018. As put by Rees, this project reveals “a massive collection of human emotion hidden within rows and rows of numbers.”

Revealing the overwhelming magnitude of loss from U.S. gun deaths, Kim Rees

Filters like race, sex, gun type, relationship to killer, and age can be used to visualize sets within the data.

Project 06 – Abstract Clock

sketchDownload
// this program draws a 24/hour abacus that keeps track of day and time. 
// each row of the abacus represents a different time-telling parameter:
var rowHeight = [-140, -74, -8, 58, 125];	//  [month, day, hr, min, sec]
var rowColor;
var beadw = 7;			// width of beads 
// arrays will be filled with bead objects:
var monthBeads = [];
var dayBeads = [];
var hrBeads = [];
var minBeads = [];
var secBeads = [];
var resetMonth = [];
var resetDay = [];
var resetHr = [];
var resetMin = [];
var resetSec = [];
var allRows = [monthBeads, dayBeads, hrBeads, minBeads, secBeads];


function setup() {
    createCanvas(480, 480);
    background(220);
    rectMode(CENTER);
    
    rowColor = [color(145, 110, 205), 		// [month
				color(200, 140, 205), 		//  day
				color(145, 140, 255), 		//  hour
				color(145, 185, 215), 		//  minute
				color(145, 200, 130)];		//  second]

	// populate monthBeads array with 12 beads:
	for (i=0; i<12; i++) {							
		monthBeads[i] = {x: beadw*i, y: rowHeight[0]}
		arrayCopy(monthBeads, resetMonth);
	}
	// populate dayBeads array with 31 beads:
	for (i=0; i<31; i++) {								
		dayBeads[i] = {x: beadw*i, y: rowHeight[1]}
		arrayCopy(dayBeads, resetDay);
	}
	// populate hrBeads array with 24 beads:
	for (i=0; i<24; i++) {
		hrBeads[i] = {x: beadw*i, y: rowHeight[2]}
		arrayCopy(hrBeads, resetHr);
	}
	// populate minBeads array with 30 beads:
	for (i=0; i<30; i++) {
		minBeads[i] = {x: beadw*i, y: rowHeight[3]}
		arrayCopy(minBeads, resetMin);
	}
	// populate secBeads array with 30 beads:
	for (i=0; i<30; i++) {
		secBeads[i] = {x: beadw*i, y: rowHeight[4]}
		arrayCopy(secBeads, resetSec);
	}
}

function draw() {
	background(220);
	
	//center canvas
    translate(width/2, height/2);

    // draw frame and rows of abacus
	drawAbacus();
	
	// draw all of the beads using the array for their row:
	for (j=0; j<allRows.length; j++) {
		setBeads(allRows[j]);
	}
	
	// check the time and move the beads accordingly:
	moveBeads(monthBeads, month(), 0, resetMonth);
	moveBeads(dayBeads, day(), 1, resetDay);
	moveBeads(hrBeads, hour(), 2, resetHr);
	moveBeads(minBeads, minute(), 3, resetMin);
	moveBeads(secBeads, second(), 4, resetSec);	
	//noLoop();
}

//Draws the frame of the abacus:
function drawAbacus() {
	fill(80, 55, 30);
	//base:
    quad(-195,190, 195,190, 220,215, -220,215);
    quad(-195,190, 195,190, 200,200, -200,200);	
    quad(-200,200, 200,200, 220,220, -220,220);	
	//left post:
    quad(-190,-200, -185,-210, -192,-210, -199,-200);
    quad(-190,200, -185,190, -185,-210, -190,-200);
    rect(-195, 0, 7, 400);
	//right post:
    quad(190,-200, 185,-210, 192,-210, 199,-200);
    quad(190,200, 185,190, 185,-210, 190,-200);
    rect(195, 0, 7, 400);
    //rows for beads:
    for (i=0; i<rowHeight.length; i++) {
        rect(0, rowHeight[i], 375, 5);
    }
	//labels:
	textSize(16);
	textFont('monospace');
	fill(0);
    text('month', -181, rowHeight[0]+28);
    text('day', -181, rowHeight[1]+28);
    text('hour', -181, rowHeight[2]+28);
    text('minute', -181, rowHeight[3]+28);
    text('second', -181, rowHeight[4]+28);
}


// draws all of the beads into their rows
function setBeads(beadArray) {
    fill(rowColor[j]);				// color taken from for loop in draw function
    strokeWeight(1.5);
	for (i=0; i<beadArray.length; i++) {		
		if (beadArray.length == 31) { ellipse(beadArray[i].x-30, beadArray[i].y, beadw, 30) }
	    else if (beadArray.length == 30) { ellipse(beadArray[i].x-23, beadArray[i].y, beadw, 30) }
	    else if (beadArray.length == 24) { ellipse(beadArray[i].x+19, beadArray[i].y, beadw, 30) }
	    else if (beadArray.length == 12) { ellipse(beadArray[i].x+103, beadArray[i].y, beadw, 30) }        
	}
}

// checks time and moves the corresponding beads:
function moveBeads(row, func, ind, rowReset) {
	var dist;
	if (row.length == 12) {dist = 283 }		//different distances based on number of beads in the row
	if (row.length == 31) {dist = 150 }
	if (row.length == 24) {dist = 199 }
	if (row.length == 30) {dist = 157 }
	for (i=0; i<func; i++) {
		// the minutes and seconds beads will move every 2x they are passed bc it looks better like that:
		if (dist == 157) {
			row[i/2] = {x: beadw*i/2-dist, y: rowHeight[ind]}
		} else {						
		    row[i] = {x: beadw*i-dist, y: rowHeight[ind]}
		}
		// february has 28 days:
		if (func == 28 & row.length == 31 && month()==2) { resetBeads(rowReset, monthBeads) }
		// 30 days has september, april, june, and november:
		if (func == 30 && row.length == 31 && month()==9) { resetBeads(rowReset, monthBeads) }
		if (func == 30 && row.length == 31 && month()==4) { resetBeads(rowReset, monthBeads) }
		if (func == 30 && row.length == 31 && month()==6) { resetBeads(rowReset, monthBeads) }
		if (func == 30 && row.length == 31 && month()==11) { resetBeads(rowReset, monthBeads) }

	}
	// once the time parameter resets to 0, so do the beads:
	if (func == 0) { resetBeads(rowReset, row) }
}
			
// sets the beads back to their original position, by row:
function resetBeads(rowReset, row) {
    arrayCopy(rowReset, row);

}

This project took me way longer than I had hoped, but I am so so pleased with the outcome. After thinking about a few ideas, I decided to make an abacus because I think it would be very helpful for me personally to visualize time in that way. I struggled a lot to get the beads where they are supposed to go, but eventually I figured out how to use the arrayCopy function to my advantage.

initial sketch of the project