Final Project-Urban Wildlife-Veronica Wang

In this interactive game/animation:
(click on the canvas first)
Press left arrow to add birds, press right arrow to add chimneys. Bird nests and raccoons will show up according to the added elements. The position of mouseY changes the color of the sky. As more birds are added, bird sound gets louder.

sketch

//Veronica Wang
//Section B
//yiruiw@andrew.cmu.edu
//Final Project

var Y_AXIS = 1; //gradient sky axis
var c1, c2; //gradient sky colors
var PREFIX = "https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/";
//var PREFIX = "";
var birdSound;
var trainSound;
var bV = 0.2;
var houseList = []; //array of houses
var allTrees = []; //trees
var birdList = []; //birds
var raccoonList = []; //raccoons
var flap = true;
var nestCount = 0;
var birdperNest = 3; //for every three birds draw a nest
var trainList = []; //trains
var rachouseCount = 0;

frameCount = 200;


function preload(){
    birdSound = loadSound(PREFIX + "birds.wav");
    trainSound = loadSound(PREFIX + "train.wav");
    trainSound.setVolume(0.5);
}

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

    //sky colors
    c1 = color(244, 248, 255); //light blue
    c2 = color(94, 164, 186); //dark teal blue

    drawBackground();

    //draw houses
    for(var i = 0; i < 10; i++) {
        var newX = random(80, 420);
        var newY = random(120, 200);
        if (houseList.length < 1){
            houseList.push(makeHouse(newX, newY));
        } 
        else{
            while (checkifLess(newX, newY, houseList, 30)){
                newX = random(80, 420);
                newY = random(120, 200);
            }
            houseList.push(makeHouse(newX, newY));
        }
    }

    //draw trees
    for(var i = 0; i < 40; i++) {
        allTrees.push(makeTree());
    }
    sortTree(allTrees);
    sortTree(houseList);

    //draw creatures
    birdList.push(makeBird(width / 2,height / 2, 15));
    raccoonList.push(makeRac(width / 2, height / 2, 5));


}

function draw() {
    //play sound
    birdSound.setVolume(bV);
    if (frameCount % 100 == 0){
        birdSound.play();
    }
    //set volume to be proportional to number of birds
    bV = (map(birdList.length, 0, 20, 0, 1));
    

    drawBackground();
    drawStairs();

    //initial number of artificial nests
    nestCount = 0;
    rachouseCount = 0;

    //raccoon house counter
    for (var i = 0; i < houseList.length; i++) {
        if (houseList[i].rh){
            rachouseCount += 1;
        }   
    }

    //add raccoons
    if (raccoonList.length < rachouseCount * 2){
        raccoonList.push(makeRac(random(0, 500), random(150, 220)));
    }

    //drawing raccoons and removing them from array if out of canvas
    for (var i = 0; i < raccoonList.length; i++) {
        raccoonList[i].draw();
        raccoonList[i].rx += raccoonList[i].vel;
        if (racoutofBound(raccoonList[i])){
            raccoonList.splice(i, 1);
            i -= 1;
        }       
    }

    //draw houses
    for (var i = 0; i < houseList.length; i++) {
        houseList[i].draw();     
    }

    //draw trees
    for (var i = 0; i < allTrees.length; i++){
        if (i % 3 == 0){
            allTrees[i].draw1();
        } else if (i % 3 == 1){
            allTrees[i].draw2();
        } else {
            allTrees[i].draw3();
        }
        if (allTrees[i].nest){
            nestCount += 1;
        }
    }

    drawTracks();
    
    //bird wing flap
    if (frameCount % 40 == 0){
        flap = !(flap);
    }

    //if bird is out of canvas, take it out of the array
    for (var i = 0; i < birdList.length; i++) {
        if (birdoutofBound(birdList[i])){
            birdList.splice(i, 1);
            i -= 1;
        }
    }

    //draw birds
    for (var i = 0; i < birdList.length; i++) {
        if (flap){
            birdList[i].draw2();
        } else {
            birdList[i].draw();
        }
        birdList[i].bx += birdList[i].xvel * noise(1);
        birdList[i].by += birdList[i].yvel * noise(1);
    };

    //adding random movement and scale birds
    if (frameCount % 100 == 0){
        for (var i = 0; i < birdList.length; i++) {
            birdList[i].xvel += random(-3, 3) * (map(pow(2, birdList[i].by / 50), 0, pow(2, 300 / 50), 1, 100)) / 35;
            birdList[i].yvel += random(-1, 1) * (map(pow(2, birdList[i].by / 50), 0, pow(2, 300 / 50), 1, 100)) / 35;

        }
    }
    

    //night sky filter
    var darkness = map(mouseY, 0, height, 0, 200);
    fill(0, 0, 0, darkness);
    noStroke();
    rect(0, 0, width, height);

    //add nest to empty trees
    if (birdList.length > 0 & int(birdList.length / birdperNest) < 40){
        if (int(birdList.length / birdperNest) > nestCount){
            var seltree = int(random(0, allTrees.length));
            while (allTrees[seltree].nest){
                seltree = int(random(0, allTrees.length));
            }
            allTrees[seltree].nest = true;
        } else if (int(birdList.length / birdperNest) < nestCount){
            var seltree = int(random(0, allTrees.length));
            if (allTrees[seltree].nest == true){
                allTrees[seltree].nest = false;
            }
            
        }
    }

    //draw train
    if (frameCount % 400 == 0) {
        trainList.push(makeTrain());
        trainSound.play();
    }
    for (var i = 0; i < trainList.length; i++) {
        trainList[i].x -= trainList[i].vel;
        trainList[i].draw();
    };
    if (trainList.length > 0){
        if (trainList[0].x < -750){
            trainList.splice(0, 1);
        }
    }
}

//if raccoon is out of canvas
function racoutofBound(bird){
    if (bird.rx < 0){
        return true;
    } else if (bird.rx > width){
        return true;
    } else if (bird.ry < 0){
        return true;
    } else if (bird.ry > height){
        return true;
    }
    return false;
}

//if bird is out of canvas
function birdoutofBound(bird){
    if (bird.bx < 0){
        return true;
    } else if (bird.bx > width){
        return true;
    } else if (bird.by < 0){
        return true;
    } else if (bird.by > height){
        return true;
    }
    return false;
}

//sort tree order by y position
function sortTree(treelist){
    n = treelist.length;
    for (var i = 0; i < n; i++){
        for (var j = 0; j < n - i - 1; j++){
            
            if (treelist[j].y > treelist[j+1].y){
                var temp = treelist[j];
                treelist[j] = treelist[j+1];
                treelist[j+1] = temp;
            }
        }
    }
}

//draw tree outside of houses
function ispointIn(x, y, house){

    var x1 = house.x;
    var x2 = house.x + house.w;
    var y1 = house.y - house.w / 3;
    var y2 = house.y + house.h * house.f;
    if (x < x1){
        return false;
    } else if (x > x2){
        return false;
    } else if (y < y1){
        return false;
    } else if (y > y2 + 13){
        return false;
    }
    return true;
}

//goes through list to check if point is outside the boundary
function hListcheck(x, y, list, n){
    if (list.length < 1){
        return false;
    }
    if (n < list.length - 2){
        return (ispointIn(x, y, list[n]) || hListcheck(x, y, list, n + 1));
    } else {
        return ispointIn(x, y, list[n]);
    }
}

//make sure houses are not overlapping
function checkifLess(x, y, list, mindist){
    var chck = false;
    for (var i = 0; i < list.length; i++) {
        var objdist = dist(x, y, list[i].x, list[i].y);
        if (objdist < mindist){
            chck = true;
        }
    }
    return chck;
}

//house object
function makeHouse(locX, locY){
    var num = 75;
    var house = { x: locX,
                  y: locY,
                  w: map(pow(2, locY / num), 0, pow(2, 300 / num), 1, 100),  //proportionally scale house in the distance
                  f: floor(random() * 2 + 1), //random number of floors from 1-3
                  rh: false, //raccoon houses
                  draw: drawHouse
                };
    return house;
}

function drawHouse(){
    noStroke();
    fill(173, 110, 110);
    var h = this.w / 2;
    rect(this.x, this.y, this.w, h * this.f); //body of house
    rect(this.x + this.w * 0.7, this.y - this.w / 3, 
        this.w / 5, this.w / 3); //chimney
    triangle(this.x, this.y, 
            this.x + this.w / 2, this.y - this.w / 3, 
            this.x + this.w, this.y); //roof
    if(this.f == 1){
        //gradient
        for (var i = 0; i < this.y / 10; i++) { 
            var op2 = map(i, 0, 50, 0, 200);
            stroke(201, 141, 141, op2); 
            line(this.x, i * this.y * 0.005 + this.y, 
                this.x + this.w, i * this.y * 0.005 + this.y);
        }
        drawWindow(this.x + this.w / 5, this.y + this.w / 6, 
                this.w * 0.3, this.w / 5);
        drawShrub(this.x, this.y + h, 
                this.w * 0.5, this.w * 0.3);

    }else{
        //gradient
        for (var i = 0; i < this.y / 5; i++) { 
            var op3 = map(i, 0, 50, 0, 200);
            stroke(201, 141, 141, op3); 
            line(this.x, i * this.y * 0.005 + this.y, 
                this.x + this.w, i * this.y * 0.005 + this.y);
        }
        drawWindow(this.x + this.w / 5, this.y + this.w / 6, 
                this.w * 0.3, this.w / 5);
        drawWindow(this.x + this.w / 5, this.y + this.w * 0.6, 
                this.w * 0.3, this.w / 5);
        drawShrub(this.x, this.y + this.w, 
                this.w * 0.4, this.w * 0.3);
    } 

    if(this.rh){
        fill(135, 81, 132);
        rect(this.x + this.w, 
             this.y + h / 5, this.w / 6, (h * this.f) * 0.75);
        triangle(this.x + this.w, this.y + h * this.f, 
                 this.x + this.w, this.y + (h * this.f) / 2, 
                 this.x + this.w + this.w / 3, this.y + h * this.f);
    }
}

function drawWindow(x, y, w, h){
    noStroke();
    fill(239, 233, 218);
    rect(x, y, w, h);
}

function drawShrub(x, y, w, h){
    noStroke();
    fill(143, 168, 104);
    ellipse(x, y, w, h);
    ellipse(x + 5, y, w * 0.8, h * 0.5);
    ellipse(x + 3, y - 2, w * 0.8, h * 0.8);

}

function makeTree(){
    var num1 = 75;

    var tempY = random(120, 200);
    var tempX = random(0, 450)
    var mult = map(pow(2, tempY / num1), 0, pow(2, 300 / num1), 1, 100) / 40;

    var tX = tempX + 3 * mult;
    var tY = tempY + 30 * mult;

    while (hListcheck(tX, tY, houseList, 0)){
        tempY = random(120, 200);
        tempX = random(0, 450)
        tX = tempX + 3 * mult;
        tY = tempY + 30 * mult;
    }
    
    var tree = { x: tempX,
                 y: tempY,
                 w: map(pow(2, tempY / num1), 0, pow(2, 300 / num1), 1, 100),  //proportionally scale house in the distance
                 m: map(pow(2, tempY / num1), 0, pow(2, 300 / num1), 1, 100) / 40,
                 draw1: tree1draw,
                 draw2: tree2draw,
                 draw3: tree3draw,
                 nest: false
                };
    return tree;

}


function tree1draw(){
    noStroke();
    push();
    
    translate(this.x, this.y);
    scale(this.m, this.m);
    fill(122, 98, 66);
    rect(3, 0, 3, 30);
    fill(95, 140, 99, 180);
    ellipse(0, -5, 30, 35);
    ellipse(15, -5, 20, 25);
    ellipse(5, 8, 50, 20);
    if(this.nest == true){
        fill(89, 68, 49);
        rect(0, 0, 10, 10);
        fill(193, 164, 137);
        ellipse(5, 5, 4, 4);
    }
    pop();
}

function tree2draw(){
    
    noStroke();
    push();
    translate(this.x, this.y);
    scale(this.m, this.m);
    fill(122, 98, 66);
    rect(5, 0, 3, 30);
    fill(95, 120, 96, 200);
    triangle(18, 18, -6, 18, 6, -30);
    if(this.nest == true){
        fill(89, 68, 49);
        rect(0, 0, 10, 10);
        fill(193, 164, 137);
        ellipse(5, 5, 4, 4);
    }
    pop();

}

function tree3draw(){
    noStroke();
    push();
    var mult = this.w / 40;
    translate(this.x, this.y);
    scale(mult, mult);
    fill(122, 98, 66);
    rect(3, 0, 3, 30);
    fill(108, 132, 102, 200);
    ellipse(4, -17, 20, 20);
    ellipse(4, -8, 30, 20);
    ellipse(4, 5, 40, 25);
    if(this.nest == true){
        fill(89, 68, 49);
        rect(0, 0, 10, 10);
        fill(193, 164, 137);
        ellipse(5, 5, 4, 4);
    }
    pop();

}

function drawBackground(){

    //draw background gradient
    setGradient(0, 0, width, height * 0.45, c1, c2, Y_AXIS);

    //mountain layer 1
    noStroke();
    fill(75, 137, 138);
    beginShape();
    curveVertex(0, height);
    curveVertex(0, height);
    curveVertex(0, 200);
    curveVertex(0, 180);
    curveVertex(190, 60);
    curveVertex(280, 80);
    curveVertex(350, 70);
    curveVertex(420, 100);
    curveVertex(520, 80);
    curveVertex(width, height);
    curveVertex(width, height);
    endShape();
    
    //gradient mask
    for (var i = 0; i < 500; i++) { 
        var op = map(i, 100, 500, 0, 255);
        stroke(255, 255, 255, op); 
        line(0, i * 0.6, width, i * 0.6);
    }

    //mountain layer 2
    noStroke();
    strokeWeight(1);
    fill(75, 147, 154);
    beginShape();
    curveVertex(0, height);
    curveVertex(0, height);
    curveVertex(0, 120);
    curveVertex(0, 110);
    curveVertex(100, 70);
    curveVertex(200, 130);
    curveVertex(300, 90);
    curveVertex(400, 130);
    curveVertex(500, 120);
    curveVertex(500, 130);
    curveVertex(width, height);
    curveVertex(width, height);
    endShape();

    //gradient mask
    for (var i = 0; i < 500; i++) { 
        var op = map(i, 100, 500, 0, 255);
        stroke(255, 183, 80, op); 
        line(0, i, width, i);
    }

}

function drawStairs(){
    noStroke();
    fill(99, 88, 77);
    for (var i = 0; i < 15; i++) {
        rect(i * 5 + 30, i * 3 + 190, 8, 1.5);
        rect(i * 5 + 40, -i * 2 + 190, 8, 1);
        rect(i * 5 + 30, i * 3 + 120, 1 + i * 0.5, 1)
    };

    rect(35, 190, 2, 45);
    rect(28, 190, 2, 45);
    rect(28, 210, 8, 2);

    rect(110, 162, 1, 35);
    rect(116, 162, 1, 35);
    rect(110, 170, 6, 1);

    strokeWeight(2);
    line(1, 1, 100, 100);

    for (var j = 0; j < 30; j++) {
        fill(117, 107, 98);
        rect(j * 2 + 440, j * 3 + 130, 1 + j * 0.7, 1);
    };

}

function drawTracks(){
    stroke(122, 102, 82);
    strokeWeight(2);
    line(0, 280, 500, 280);
    strokeWeight(1);
    line(0, 275, 500, 275);

    for (var i = 0; i < 15; i++) {
        rect(i * 40, 273, 1, 6);
    };
}

function makeTrain(){
    var train = { x: 500,
                  y: 277,
                  vel: random(5, 15),
                  draw: drawTrain
                };
    return train;
}

function drawTrain(){
    noStroke();
    fill(80);
    triangle(this.x, this.y - 5, this.x + 25, this.y - 5, this.x + 25, this.y - 20);
    rect(this.x + 12, this.y - 40, 10, 15);
    rect(this.x + 20, this.y - 40, 50, 35);
    rect(this.x + 22, this.y - 50, 15, 25);
    rect(this.x + 55, this.y - 59, 30, 4);
    rect(this.x + 70, this.y - 16, 16, 8);
    rect(this.x + 80, this.y - 16, 8, 8);
    rect(this.x + 88, this.y - 13, 18, 3);
    fill(140, 89, 88);
    rect(this.x + 16, this.y - 45, 40, 25);
    fill(140, 100, 88);
    rect(this.x + 60, this.y - 55, 20, 45);
    fill(201, 216, 215);
    rect(this.x + 64, this.y - 52, 12, 18);
    fill(96, 83, 58);
    ellipse(this.x + 30, this.y - 5, 15, 15);
    ellipse(this.x + 70, this.y - 5, 15, 15);

    for (var i = 1; i < 5; i++) {
        fill(80);
        rect(this.x + 100 * i, this.y - 55, 90, 3);
        rect(this.x + 100 * i, this.y - 13, 100, 3);
        fill(140, 120, 88);
        rect(this.x + 5 + 100 * i, this.y - 52, 80, 45);
        fill(140, 130, 98);
        rect(this.x + 5 + 100 * i, this.y - 22, 80, 15);
        fill(201, 216, 215);
        rect(this.x + 12 + 100 * i, this.y - 48, 30, 18);
        rect(this.x + 48 + 100 * i, this.y - 48, 30, 18);
        fill(96, 83, 58);
        ellipse(this.x + 20 + 100 * i, this.y - 5, 15, 15);
        ellipse(this.x + 70 + 100 * i, this.y - 5, 15, 15);
    };

    fill(0);
    drawSmoke(this.x, this.y);

}

function drawSmoke(x, y){
    fill(255, 255, 255, 100);
    ellipse(x + 30, y - 60, 20, 10);
    ellipse(x + 50, y - 70, 15, 8);
}


function makeBird(x, y) {
    var num = 50;
    var bird = {"bx": x, 
                "by": y, 
                "bsz": size, 
                "bsca": (map(pow(2, y / num), 0, pow(2, 300 / num), 1, 100)) / 35,
                "xvel": random(-10, 10) * (map(pow(2, y / num), 0, pow(2, 300 / num), 1, 100)) / 35,
                "yvel": random(-5, 5) * (map(pow(2, y / num), 0, pow(2, 300 / num), 1, 100)) / 35,
                };
    bird.draw = birdDraw;
    bird.draw2 = birdDraw2;
    return bird;
}

function birdDraw(){
    noFill();
    stroke(0);
    strokeWeight(2);
    push();
    translate(this.bx, this.by);
    scale(this.bsca);
    ellipseMode(CENTER); 
    arc(35, 35, 100, 100, PI * 1.25, PI * 1.5);
    arc(-35, 35, 100, 100, PI * 1.5, PI * 1.75);
    fill(0);
    ellipse(0, 0, 5, 5); 
    pop();
}

function birdDraw2(){
    noFill();
    stroke(0);
    strokeWeight(2);
    push();
    translate(this.bx, this.by);
    scale(this.bsca);
    rotate(PI/10);
    arc(35, 35, 100, 100, PI * 1.25, PI * 1.5);
    fill(0);
    ellipse(0, 0, 5, 5); 
    pop();

    push();
    translate(this.bx, this.by);
    scale(this.bsca);
    rotate(-PI / 10);
    arc(-35, 35, 100, 100, PI * 1.5, PI * 1.75);
    pop();
}

//make raccoon
function makeRac(x, y){
    var rac;
    var num = 50;
    rac = {
        "rx": x ,
        "ry": y, 
        "rsz": size,
        "bsca": (map(pow(2, y / num), 0, pow(2, 300 / num), 1, 100)) / 400,
        "vel": random(-2, 2) * (map(pow(2, y / num), 0, pow(2, 300 / num), 1, 50)) / 35
    };
    rac.draw = racDraw;
    return rac;
}

//draw raccoon
function racDraw(){
    var inv = -1;
    push();
    translate(this.rx, this.ry);
    fill(0);
    noStroke();
    beginShape();
    if (this.vel < 0){
        scale(this.bsca, this.bsca);
    } else{
        scale(this.bsca * -1, this.bsca);
    }
    vertex(0, 0);
    vertex(5, -10);
    vertex(3, -20);
    vertex(7, -30);
    vertex(5, -40);
    vertex(8, -45);
    vertex(20, -40);
    vertex(65, -55);
    vertex(85, -65);
    vertex(150, -60);
    vertex(190, -30);
    vertex(180, 20);
    vertex(190, 50);
    vertex(180, 80);
    vertex(170, 80);
    vertex(170, 75);
    vertex(175, 73);
    vertex(176, 55);
    vertex(140, 25);
    vertex(110, 25);
    vertex(80, 80);
    vertex(70, 80);
    vertex(70, 75);
    vertex(75, 75);
    vertex(80, 50);
    vertex(70, 10);
    vertex(50, 10);
    vertex(30, 5);
    vertex(10, 10);
    vertex(0, 0);
    endShape();

    beginShape();
    vertex(200, -25);
    vertex(192, 10);
    vertex(200, 18);
    vertex(210, -20);
    endShape();

    beginShape();
    vertex(220, -15);
    vertex(230, -8);
    vertex(220, 30);
    vertex(210, 22);
    endShape();

    beginShape();
    vertex(240, -3);
    vertex(250, 5);
    vertex(242, 20);
    vertex(232, 25);
    endShape();

    beginShape();
    fill(255);
    vertex(50, 10);
    vertex(30, -15);
    vertex(20, -13);
    vertex(15, -5);
    vertex(15, -5);
    vertex(20, 0);
    vertex(26, 0);
    vertex(35, 6);
    endShape();

    fill(0);
    ellipse(23, -8, 5, 5);
    rect(112, 50, 15, 7);

    rotate(PI / 4);
    rect(60, -40, 10, 40);
    rect(120, -80, 10, 30);
    pop();
}

//interactions
function keyPressed(){
    if(keyCode === RIGHT_ARROW){
        if (rachouseCount < houseList.length){
            var selhouse = int(random(0, houseList.length));
            while (houseList[selhouse].rh == true){
                selhouse = int(random(0, houseList.length));
            }
            houseList[selhouse].rh = true;
        }
    }
    if(keyCode === LEFT_ARROW){
        birdList.push(makeBird(random(0, 500), random(0, 200)));
    }
}

//Linear gradient code from p5js examples https://p5js.org/examples/color-linear-gradient.html
function setGradient(x, y, w, h, c1, c2, axis) {
    noFill();
    if (axis == Y_AXIS) {  // Top to bottom gradient
        for (var i = y; i <= y + h; i++) {
            var inter = map(i, y, y + h, 0, 1);
            var c = lerpColor(c1, c2, inter);
            stroke(c);
            line(x, i, x + w, i);
        }
    }  
}

In this project I want to create an interactive animation on cohabitation wiht urban wildlife in cities. As we are displacing natural habitats to make way for urban sprawl, effects of a loss of biodiversity and edge effects from habitat fragmentation are becoming more pronounced. In my studio I am currently working on a thesis project dealing with the environment and cohabitation/negotiation of boundaries between human and more-than-human species, and creating installations for endangered species as well as synanthropic species to share urban space. I am inspired by the works of Joyce Hwang and Sarah Gunawan , and I want to create an animation that documents the vision of such installation projects and their impacts on our environment. Especially the bird landing pads and composting chimneys for raccoons.

In this animation, trains pass by at a certain interval of time, pressing left arrow adds a bird, and for every 3 birds added, an artificial bird nest attached to trees will pop up. (Nests will start disappearing when birds leave the screen). Pressing right arrow adds a compost chimney attached to houses, and every chimney attracts 2 raccoons.

Working on this project I spent a large chunk of my time illustrating the creatures and houses which in hindsight could have been done much easier if I just drew them and upload them to imgur. I also had trouble with overlapping objects and had to create functions that sort the order or the object array and also draws objects outside of the boundary of other objects. I feel like I reviewed everything we learned this semester through this project.

Sound Test

 

Leave a Reply