For this week’s project, I decided to visualize an NYC subway line I used often (pre-pandemic) and the views of the skyline you can see passing by when the subway tracks are outdoors. I first roughly sketched out the scene on paper and then used Professor Cortina’s sample code as a baseline and customized it further to meet my vision. I displayed the scene to be at night since that’s the time of day I liked most when watching the skyline. Also, although nighttime, the lights would always be on in the buildings, so I decided to have lit up windows on all the buildings as well as the light reflections.
var frontBuildings = [];
var backBuildings = [];
var moonAndStars;
function preload(){
moonAndStars = loadImage("https://i.imgur.com/0JJlsGR.png");
}
function setup() {
createCanvas(480, 270);
// make initial array of buildings
for(var i = 0; i < 20; i++) {
var x = random(width);
backBuildings[i] = makeBackBuilding(x);
}
for(var i = 0; i < 10; i++) {
var x = random(width);
frontBuildings[i] = makeFrontBuilding(x);
}
frameRate(50);
}
function draw() {
// gradient night background
setGradient(color("#655BCE"), color("#1A1449"));
// moon
image(moonAndStars, 0, 0, 480, 350);
updateAndDisplayBuildings();
removeBuildingsThatHaveSlippedOutOfView();
addNewBuildingsWithSomeRandomProbability();
// tracks
drawTrack();
// train
drawTrain();
}
function drawTrain() {
strokeWeight(1);
stroke("#4A4954");
// train body
fill("#65656D");
rect(0, 180, 220, 80);
strokeWeight(5);
line(0, 240, 218, 240);
strokeWeight(2);
line(0, 245, 218, 245);
strokeWeight(5);
line(0, 190, 218, 190);
strokeWeight(2);
line(0, 196, 218, 195);
noStroke();
// doors
fill("#5D5C66");
rect(10, 200, 36, 60);
rect(130, 200, 36, 60);
// windows
fill("#383664");
rect(12, 202, 32, 30);
rect(132, 202, 32, 30);
rect(58, 200, 60, 30);
rect(200, 200, 20, 40);
// train number
fill("purple");
circle(182, 220, 25);
fill(255);
textSize(20);
text("7", 177, 227);
}
function drawTrack() {
fill(20);
rect(0, 220, width, 100);
fill("#8F8F91");
rect(0, 220, width, 30);
fill("#F0D933");
rect(0, 240, width, 5);
}
function removeBuildingsThatHaveSlippedOutOfView(){
// If a building has dropped off the left edge,
// remove it from the array. This is quite tricky, but
// we've seen something like this before with particles.
// The easy part is scanning the array to find buildings
// to remove. The tricky part is if we remove them
// immediately, we'll alter the array, and our plan to
// step through each item in the array might not work.
// Our solution is to just copy all the buildings
// we want to keep into a new array.
var frontBuildingsToKeep = [];
var backBuildingsToKeep = [];
for (var i = 0; i < frontBuildings.length; i++){
if (frontBuildings[i].x + frontBuildings[i].width > 0) {
frontBuildingsToKeep.push(frontBuildings[i]);
}
}
for (var i = 0; i < backBuildings.length; i++){
if (backBuildings[i].x + backBuildings[i].width > 0) {
backBuildingsToKeep.push(backBuildings[i]);
}
}
frontBuildings = frontBuildingsToKeep; // remember the surviving buildings
backBuildings = backBuildingsToKeep;
}
function addNewBuildingsWithSomeRandomProbability() {
// With a very tiny probability, add a new building to the end.
var newBuildingLikelihood = 0.08;
if (random(0,1) < newBuildingLikelihood) {
backBuildings.push(makeBackBuilding(width));
frontBuildings.push(makeFrontBuilding(width));
}
}
function updateAndDisplayBuildings(){
// Update the building's positions, and display them.
for (var i = 0; i < backBuildings.length; i++){
backBuildings[i].move();
backBuildings[i].display();
}
for (var i = 0; i < frontBuildings.length; i++){
frontBuildings[i].move();
frontBuildings[i].display();
}
}
// method to update position of building every frame
function frontBuildingMove() {
this.x += this.speed;
}
// draw the building and some windows
function frontBuildingDisplay() {
noStroke();
// building
fill(this.color);
var y = 220 - this.height;
rect(this.x, y, this.width, this.height);
// windows
fill("#FFE9AD");
for(var i = 0; i < 5; i ++) {
rect(this.x + 5, y + 10 + 15*i, this.width/6, 8);
rect(this.x + 20, y + 10 + 15*i, this.width/6, 8);
rect(this.x + 35, y + 10 + 15*i, this.width/6, 8);
}
}
function makeFrontBuilding(xlocation) {
var building = {x: xlocation,
width: random(40, 50),
height: random(80, 120),
color: color(random(20, 70), random(20, 70), 130),
speed: -3,
move: frontBuildingMove,
display: frontBuildingDisplay}
return building;
}
// method to update position of building every frame
function backBuildingMove() {
this.x += this.speed;
}
// draw the back building and some windows
function backBuildingDisplay() {
noStroke();
var y = 220 - this.height;
// light reflections
fill(255, 243, 180, 20);
ellipse(this.x + this.width/2, y + 20, this.width*2);
// building
fill(this.color);
rect(this.x, y, this.width, this.height);
// windows
fill("#FFE9AD");
for(var i = 0; i < 8; i ++) {
rect(this.x + 5, y + 10 + 15*i, this.width/6, 8);
rect(this.x + 20, y + 10 + 15*i, this.width/6, 8);
rect(this.x + 35, y + 10 + 15*i, this.width/6, 8);
}
}
function makeBackBuilding(xlocation) {
var building = {x: xlocation,
width: random(40, 50),
height: random(130, 160),
color: color(random(60, 80), random(60, 80), 180),
speed: -3,
move: backBuildingMove,
display: backBuildingDisplay}
return building;
}
// code from https://editor.p5js.org/REAS/sketches/S1TNUPzim
function setGradient(c1, c2) {
noFill();
for (var y = 0; y < height; y++) {
var inter = map(y, 0, height, 0, 1);
var c = lerpColor(c1, c2, inter);
stroke(c);
line(0, y, width, y);
}
}