xxxxxxxxxx
// Window Drop
// Eric Mika, 2010
// https://frontiernerds.com/window-drop
// Ported to p5.js from the original Processing version: https://openprocessing.org/sketch/7223
// noprotect
let gui;
let fancyGraphics = true;
let dropsWanted = 40;
let drops = [];
let sceneryImage;
let scenery; // Scenery
let carSpeed = 1;
let carWindow; // Graphics
function preload() {
sceneryImage = loadImage("scenery.jpg");
}
function setup() {
pixelDensity(1);
createCanvas(900, 500);
frameRate(60);
smooth();
background(40);
carWindow = createGraphics(width, height);
carWindow.background(128);
scenery = new Scenery();
gui = QuickSettings.create(15, 15, "Window Drop");
gui.addRange("Car Speed", 0, 10, carSpeed, 1, (value) => (carSpeed = value));
gui.addRange("Drops Wanted", 0, 100, dropsWanted, 1, (value) => (dropsWanted = value));
gui.addBoolean("Graphics", fancyGraphics, (value) => {
fancyGraphics = value;
carWindow.background(128);
});
}
function draw() {
// Maintain the drop population.
while (drops.length < dropsWanted) {
drops.push(new Drop());
}
// Draw the scrolling background.
scenery.update();
if (fancyGraphics) {
scenery.display();
}
carWindow.reset();
carWindow.push();
carWindow.fill(128, 5);
carWindow.rect(0, 0, width, height);
carWindow.pop();
if (fancyGraphics) {
tint(255, 255, 255, 100);
image(carWindow, 0, 0);
noTint();
} else {
image(carWindow, 0, 0);
}
for (let i = drops.length - 1; i >= 0; i--) {
let aDrop = drops[i];
// Update the drop.
aDrop.update();
aDrop.checkEdges();
aDrop.display(carWindow);
// Clean up the dead.
if (aDrop.killMe) {
drops.splice(i, 1);
}
}
// Check collisions.
for (let i = 0; i < drops.length; i++) {
for (let j = 0; j < drops.length; j++) {
if (i != j) {
let drop1 = drops[i];
let drop2 = drops[j];
if (!drop1.killMe && !drop2.killMe) {
if (p5.Vector.dist(drop1.location, drop2.location) < drop1.diameter / 2 + drop2.diameter / 2) {
drop1.speedLimit++; // Move faster now that we're bigger and heavier.
drop1.diameter += random(1, 5); // Grow the drop.
drop2.killMe = true; // The other drop can go now.
}
}
}
}
}
}
// -----------------------
class Scenery {
constructor() {
this.scene = sceneryImage;
this.sceneBuffer = createImage(width, height, RGB);
this.location = new createVector(0, height - this.scene.height);
this.velocity = new createVector(0, 0);
this.acceleration = new createVector(0, 0);
this.update = function () {
// set acceleration based on perlin noise
this.velocity.x = carSpeed * -10;
this.velocity.add(this.acceleration);
this.location.add(this.velocity);
};
this.display = function () {
// TODO motion blur
if (this.location.x + this.scene.width < 0) this.location.x = 0;
image(this.scene, this.location.x, this.location.y);
image(this.scene, this.location.x + this.scene.width, this.location.y);
};
}
}
// -----------------------
class Drop {
constructor() {
this.killMe = false; // boolean
this.noiseStep = 0.2; // float
this.noiseIndex = 0; // float
this.diameter = 16; // int
this.speedLimit = 3; // float
// set location, from top and left edge of screen
if (random(1) < 0.5) {
// from the top
this.location = createVector(random(width), -10);
} else {
// from the right
this.location = createVector(width + 10, random(height));
}
this.velocity = createVector(0, 0);
this.acceleration = createVector(0, 0);
this.update = function () {
this.acceleration = createVector(random(-1, 1), random(-1, 1));
this.acceleration.x -= carSpeed / 10;
this.acceleration.normalize();
this.acceleration.mult(noise((this.noiseIndex += this.noiseStep)) * 3);
this.velocity.add(this.acceleration);
this.velocity.x = constrain(this.velocity.x, -3, 3); // limit x velocity
this.velocity.y = constrain(this.velocity.y, 0, this.speedLimit); // limit y velocity
this.location.add(this.velocity);
};
this.display = function (target) {
// TODO a tail of diminishing size to get the drop shape?
if (fancyGraphics) {
// smear the carWindow
target.fill(0);
target.noStroke();
target.ellipse(this.location.x, this.location.y, this.diameter, this.diameter);
} else {
// simple square
target.stroke(1);
target.fill(200);
target.rect(this.location.x, this.location.y, 16, 16);
}
};
this.checkEdges = function () {
if (this.location.y > height) {
// mark for deletion
this.killMe = true;
}
};
}
}