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.
/* 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();
}
}