dam it!

This project is inspired by the tensions between dams, the environment, and people displaced or otherwise affected by dams.

In this animation, you become the dam builder. By pressing the mouse anywhere on the canvas, a dam emerges in the river, affecting the water level, marine life (fish and plants), and nearby communities (houses). Moving the mouse from left to right raises the dam and increases its impact, killing fish and making houses disappear. Pressing the space bar replenishes the fish population. Clicking the mouse advances to different facts about dams, stored in an array. These facts are adapted from research summarized by Earth Law Center and HuffPost.

sketch
/* jaden luscher
jluscher
section a
dammit(!): an educational animation */

var waterlevel1; // water level left of dam (upstream)
var waterlevel2; // water level right of dam (downstream)
var damX = 100;	// x location of dam

var fish = [];
var numFish = 40;

var house = []; // original array of houses
var newhouseProbability = 0.03; // likelihood of a new house popping up

var plants = [];
var plantNum = 100;

var sceneNum = 0;   // main reference for changing objects
var factNum = 0;    // determines index of damFacts array
var damFacts = ['what a beautiful ecosystem... click to screw it up!',
    'great. you built a dam. the river’s flow is restricted. click to learn more.',
    'the united states has over 9,000 dams...',
    '...many are not equipped to handle the amount of water that could result from climate change.',
    'pennsylvania has 145 high hazard dams in poor or unsatisfactory condition.',
    'dams have fragmented two thirds of the worlds large rivers...',
    '...and have flooded a land area the size of california.',
    'their reservoirs contain three times as much water as all the world’s rivers, and speed up evaporation.',
    'dams disrupt fish and bird migration, both physically and chemically...',
    '...and habitat loss is the leading cause of extinction.',
    'upstream: nutrients trapped in reservoirs can cause toxic algae blooms.',
    'downstream: ecosystems suffer from a lack of sedimentation and nutrients.',
    'less nutrients = less vegetation = more erosion',
    'river deltas are deprived of the silt they need to defend against damage from the ocean.',
    'removing damaged and aging dams protects the surrounding population from disaster...',
    '...and allows the rivers to restore their natural and biological integrity.',
    'but often what happens instead is...'
    ];

function setup() {
    createCanvas(480, 360);
	noStroke();
	frameRate(20);
    angleMode(DEGREES);
    textFont("monospace");
    textSize(12);
    if (sceneNum == 0) {
        waterlevel1 = 240;
        waterlevel2 = 240;
    }
    for(var i = 0; i < numFish; i++) {
        var newFish = makeFish();
        fish.push(newFish);
    }
    // first house
    for(var i = 0; i < 10; i++) {
        var firstHouses = makehouse(random(width), 180);
        house.push(firstHouses);
    }
    for (var i = 0; i < plantNum; i++) {
        var newPlant = makePlant();
        plants.push(newPlant);
    }
}

function draw() {
    drawSky();
    if (sceneNum > 0) {     // only occurs once mouse has been pressed
        sceneNum = constrain(floor(map(mouseX, width/2, width, 1, 12)), 1, 12);
        waterlevel1 = 240 - sceneNum * 6;
        waterlevel2 = 240 + sceneNum * 9;
    }
    drawMountain(0, 0.02, 0.00004, 50, 220);   // background mountains    
    drawMountain(1, 0.01, 0.00007, 50, 220);   // midground mountains
    drawMountainAndHouses(0.005, 0.0001, 40, 220); // foreground mountains and houses

    for (var i = 0; i < house.length; i++) {
        house[i].move();
        house[i].show();  
    }
    var keepHouses = []; // stores houses in range
    // keep houses still in range 
    if (house.length > 0) {
        for (var i = 0; i < house.length; i++) {
            if (house[i].houseExists & house[i].x + house[i].w > 0) {
                keepHouses.push(house[i]);
            }
        }
    }
    house = keepHouses; // thank you Chuong!
    water();
    push();
    for (var i = 0; i < plantNum; i++) {
        if (plants[i].plantExists) {
            plants[i].show();
            translate(width / plantNum, 0);
        }
    }
    pop();
    // draw fish
    for (var i = 0; i < numFish; i++) {
        if (fish[i].fishExists) {
            fish[i].move();                
            fish[i].show();
        }
    }
    makeDam();
    spitfacts(factNum);
    if(factNum > damFacts.length - 1) {
        gameOver();
    }
}

function mousePressed() {
    sceneNum = 1;
    factNum ++;
}

function keyPressed() {
    if(key == ' ') {
        sceneNum = 0;
        waterlevel1 = 240;
        waterlevel2 = 240;
        fish = [];  // clear fish array
        for(var i = 0; i < numFish; i++) {
            var newFish = makeFish();
            fish.push(newFish);
        }
    }
}

function gameOver() {
    background("orange");
    fill(255);
    textSize(32);
    text('DAMMIT!', 170, 180);
    textSize(14);
    textAlign(CENTER);
    text('(YOU KILLED ALL THE FISH AND HAVE CAUSED SEVERE DAMAGE TO THE ECOSYSTEM)',
            40, 250, 400)
    noLoop();
}

function drawSky() {
    background("orange");
    push();
    fill("yellow");
    translate(400, 80);
    scale(map(mouseY, 0, height, 0.5, 1));
    ellipse(0, 0, 50, 50);
    pop();
}

function spitfacts(f) {
    push();
    fill(255);
    text(damFacts[f], 30, 30, 220, 80);
    pop();
}

function drawMountain(mc, a, speed, high, low) {
    noStroke();
    if(mc == 0) fill(240, 150, 120);    // background mountain color
    if(mc == 1) fill(220, 130, 100);    // midground mountain color
    if(mc == 2) fill(20, 50, 180);      // water color
    beginShape();
    vertex(width, height);
    vertex(0, height);
    for(var i = 0; i < width + 1; i ++){
        var x = (i * a) + (millis() * speed);
        var y = map(noise(x + mc*1000), 0, 1, high, low); 
        // x + mx*100 ensures mountains look different
        vertex(i, y);
    }
    endShape();
}

function drawMountainAndHouses(a, speed, high, low) {
    noStroke();
    fill(200, 100, 60);     // foreground mountain color
    beginShape();
    vertex(width, height);
    vertex(0, height);
    for(var i = 0; i < width + 1; i ++){
        var x = (i * a) + (millis() * speed);
        var y = map(noise(x), 0, 1, high, low); 
        vertex(i, y);
    }
    endShape();

    if (newhouseProbability > random(1.0)) {  
        // make new house randomly
        var newhouse = makehouse(width + 1, y);
        house.push(newhouse);
    }
}

function makehouse(px, py) {
    var newhouse = {x : px,
                y : random(220, py),
                w : random(10, 30),
                h : random(10, 30),
                rh : random(3, 15),
                c : color(random(100, 255), random(100), 0),
                dx : -1,
                houseExists: true,
                move : movehouse,
                show : showhouse}
    return newhouse;
}

function showhouse() {  // house x, y, width, height, roof height
    push();
    fill(this.c);
    rect(this.x, this.y, this.w, this.h);
    beginShape();
    vertex(this.x - 2, this.y);
    vertex(this.x + (this.w/2), this.y - this.rh); // roof peak
    vertex(this.x + this.w + 2, this.y);
    endShape();
    fill(230, 180, 120);
    rect(this.x + this.w / 2 - 4, this.y + this.h, this.w / 4, -this.h / 2); // door
    pop();
}

function movehouse() {
    this.x += this.dx;
    if(this.y + this.h > waterlevel1) {
        this.houseExists = false;
    }
}

function makePlant() {
    var plant = {x: 0,
                y: 0,
                c: color(random(50), random(100, 200), random(100)),
                n: floor(random(3, 6)),
                len: random(-5, -40),
                plantExists: true,
                show: showPlant,
    }
    return plant;
}

function showPlant() {
    push();
    translate(0, height + 10 + sceneNum * 3);
    rotate(15 / this.n)
    stroke(this.c);
    strokeWeight(6/this.n);
    for (var i = 0; i < this.n; i++) {
        line(0, 0, 0, this.len + random(-1, 1));
        rotate(-30 / this.n);
    }
    pop();
}

function makeFish() {
        var newFish = {x: random(0, width), 
                y: random(waterlevel1 + 10, height-20),
                w: int(random(10, 25)), h: int(random(5, 10)),
                dx: random(-3, 3), 
                c: color(random(100, 255), random(100), random(100)),
                fishExists: true,
                fishDead: false,
                show: showFish,
                move: moveFish,
                }
        return newFish;
}

function moveFish() {
    this.x += this.dx;
    if (sceneNum == 0) {
        // with no dam, fish change directions (off canvas)
        if(this.x > width + 20 || this.x < -20) {
            this.dx = -this.dx;
        }
    } else {
        // if fish is to the left of dam, disappears
        if(this.x < damX + 30) {
            this.fishExists = false;
        } // if fish is above water, dies
        else if(this.y < waterlevel2 + 5) {
            this.fishDead = true;
        }
        // if fish hits dam or goes off screen, change direction
        if(this.x > width + 20 || this.x <= damX + 33) {
            this.dx = -this.dx;
        }
    }
}

function showFish() {
    fill(this.c);
    ellipse(this.x, this.y, this.w, this.h);
    if(this.dx < 0) {  // facing left
        if(this.fishDead) {
            // if fish is dead, eyes make it appear belly-up
            fishEye(this.x - 3, this.y + 3, this.w / 4);
        } else {
            fishEye(this.x - 3, this.y - 3, this.w / 4, true);
        }
        triangle(this.x + this.w/3, this.y, 
                    this.x+this.w, this.y-5, 
                    this.x+this.w, this.y+5);
    } else {  // facing right
        if(this.fishDead) {
            // if fish is dead, eyes make it appear belly-up
            fishEye(this.x + 3, this.y + 3, this.w / 4);
        } else {
            fishEye(this.x + 3, this.y - 3, this.w / 4, true);           
        }
        triangle(this.x - this.w/3,this.y, 
                this.x-this.w, this.y-5, 
                this.x-this.w, this.y+5);
    }
    // if fish is dead, stay floating at water level
    if(this.fishDead) {
        this.y = waterlevel2;
        this.dx = 0;
    }
}

function fishEye(x, y, sz, alive) {
    push();
    fill(255);
    ellipse(x, y, sz, sz);
    if (alive) {
        fill(0);
        ellipse(x, y, 2, 2);
    }
    pop();
}

function water() {
    if(sceneNum == 0) { // initial river ripples using mountain function
        drawMountain(2, 0.005, 0.0005, waterlevel1+10, waterlevel1-10);
    }
    push();
    // water line shows gradient of water loss
    for(var i = 0; i < sceneNum; i++) {
        var alph = 10;     // alpha
        fill(0, alph);    // waterline
        rect(0, 240, width, height -240);
        alph += 3;
        translate(0, 10);
    }
    pop();
	fill(20, 50 + sceneNum * 6, 180 - sceneNum * 6);     // blue water
	if (sceneNum == 0) {
		rect(0, waterlevel1, width, height - waterlevel1);
	} else {
        if (factNum > 9) {   // fact 10 is about algae blooms
            fill(20, 100, 50);
        }
		// water left of dam
		rect(0, waterlevel1, damX, height - waterlevel1);
		// water right of dam
		rect(damX, waterlevel2, width - damX, height - waterlevel2);
	} 
}

function makeDam() {
	fill(180, 180, 150);
	if (sceneNum > 0) {
		beginShape();
		vertex(damX - 10, waterlevel1 - 20);
		vertex(damX + 5, waterlevel1 - 20);
		vertex(damX + 30, height);
		vertex(damX - 10, height);
		endShape();
	}
}

Leave a Reply