xxxxxxxxxx
370
/**
* Author: Youssef Abdelhamid
* Course: Decoding Nature
*
* Description:
* This sketch simulates a generative meadow. Grass sways in the wind, seeds fall and
* frow into plants, and a flock of birds navigate via emergent flocking rules.
*
* Interactions:
* - Arrow Keys: Change wind direction
* - Click & Drag: Create rain drops that accelerate plant growth
* - Mouse Movement: Influence the flock of birds
*/
let grassBlades = [];
let seeds = [];
let birds = [];
let windVector;
let rainDrops = [];
let windIntensity = 0.2;
let globalTime = 0;
function setup() {
createCanvas(windowWidth, windowHeight);
windVector = createVector(0.1, 0); // Initial gentle breeze
// Initialize grass blades
for (let i = 0; i < 300; i++) {
let x = random(width);
let y = height - random(50, 150);
grassBlades.push(new GrassBlade(x, y));
}
// Initialize initial seeds, falling from above
for (let i = 0; i < 10; i++) {
seeds.push(new Seed(random(width), random(-200, -50)));
}
// Initialize flock of birds
for (let i = 0; i < 20; i++) {
birds.push(new Bird(createVector(random(width), random(height / 2))));
}
}
function draw() {
background(220, 230, 245); // A soft sky-blue background
globalTime += 0.01;
// Update and display grass
for (let blade of grassBlades) {
blade.applyWind(windVector);
blade.display();
}
// Update seeds
for (let i = seeds.length - 1; i >= 0; i--) {
seeds[i].applyForce(createVector(windVector.x * 0.1, 0.2)); // Gravity + slight horizontal drift
seeds[i].update();
seeds[i].display();
if (seeds[i].landed && seeds[i].growthReady()) {
// Seed sprouts into a new grass blade
grassBlades.push(new GrassBlade(seeds[i].pos.x, seeds[i].pos.y - 5, true));
seeds.splice(i, 1);
}
}
// Update rain drops
for (let i = rainDrops.length - 1; i >= 0; i--) {
rainDrops[i].update();
rainDrops[i].display();
if (rainDrops[i].offScreen()) {
rainDrops.splice(i, 1);
} else {
// If close to the ground, enhance plant growth and remove drop
if (rainDrops[i].pos.y > height - 100) {
let nearby = grassBlades[floor(random(grassBlades.length))];
nearby.boostGrowth();
rainDrops.splice(i, 1);
}
}
}
// Update flock of birds
for (let bird of birds) {
bird.flock(birds);
bird.avoidMouse(createVector(mouseX, mouseY));
bird.update();
bird.display();
}
// On-screen instructions
fill(50, 100);
noStroke();
rect(10, 10, 250, 100, 8);
fill(255);
textSize(14);
text("Arrow Keys: Change Wind Direction\nClick+Drag: Create Rain\nMove Mouse: Guide Bird Flock\nObserve Seeds -> Grass Growth", 20, 30);
}
function keyPressed() {
// Adjust wind direction with arrow keys
if (keyCode === LEFT_ARROW) {
windVector = createVector(-windIntensity, 0);
} else if (keyCode === RIGHT_ARROW) {
windVector = createVector(windIntensity, 0);
} else if (keyCode === UP_ARROW) {
windVector = createVector(0, -windIntensity);
} else if (keyCode === DOWN_ARROW) {
windVector = createVector(0, windIntensity);
}
}
function mouseDragged() {
// Create rain drops at the mouse position
let r = new RainDrop(mouseX, mouseY);
rainDrops.push(r);
}
// ---------- Classes ----------
class GrassBlade {
/**
* GrassBlade represents a single plant. It sways according to noise and wind.
* @param {number} x - x position
* @param {number} y - y position
* @param {boolean} newlyGrown - whether the grass was just grown from a seed
*/
constructor(x, y, newlyGrown = false) {
this.x = x;
this.y = y;
this.baseHeight = random(40, 80);
this.noiseOffset = random(1000);
this.growthFactor = newlyGrown ? 1.2 : 1;
}
applyWind(wind) {
// Increment noise offset to animate sway
this.noiseOffset += 0.01;
}
display() {
// Create a natural sway using Perlin noise
let sway = map(noise(this.noiseOffset), 0, 1, -10, 10);
stroke(34, 139, 34);
strokeWeight(2);
let tipX = this.x + sway;
let tipY = this.y - this.baseHeight * this.growthFactor;
line(this.x, this.y, tipX, tipY);
}
boostGrowth() {
// Rain accelerates growth
this.growthFactor += 0.1;
}
}
class Seed {
/**
* Seed falls from the sky and, after landing and waiting a while, turns into a grass blade.
* @param {number} x - initial x position
* @param {number} y - initial y position (above the ground)
*/
constructor(x, y) {
this.pos = createVector(x, y);
this.vel = createVector(0, random(1, 2));
this.acc = createVector(0, 0);
this.landed = false;
this.timer = 0; // count frames after landing
}
applyForce(f) {
this.acc.add(f);
}
update() {
if (!this.landed) {
this.vel.add(this.acc);
this.pos.add(this.vel);
this.acc.mult(0);
if (this.pos.y >= height - 5) {
this.pos.y = height - 5;
this.landed = true;
}
} else {
// Once on the ground, start counting time
this.timer++;
}
}
display() {
fill(139, 69, 19);
noStroke();
ellipse(this.pos.x, this.pos.y, 5, 5);
}
growthReady() {
// After ~3 seconds on the ground (assuming ~60fps), sprout into grass
return this.timer > 180;
}
}
class RainDrop {
/**
* RainDrop falls straight down and accelerates plant growth upon hitting the ground.
* @param {number} x - mouse x position
* @param {number} y - mouse y position
*/
constructor(x, y) {
this.pos = createVector(x, y);
this.vel = createVector(0, 5);
}
update() {
this.pos.add(this.vel);
}
display() {
stroke(0, 0, 255);
strokeWeight(2);
line(this.pos.x, this.pos.y, this.pos.x, this.pos.y+5);
}
offScreen() {
return this.pos.y > height + 10;
}
}
class Bird {
/**
* Bird class implementing a Boids-like flocking behavior.
* @param {p5.Vector} position - initial position of the bird
*/
constructor(position) {
this.position = position.copy();
this.velocity = p5.Vector.random2D();
this.acceleration = createVector(0,0);
this.maxForce = 0.05;
this.maxSpeed = 2;
}
flock(birds) {
let sep = this.separate(birds);
let ali = this.align(birds);
let coh = this.cohesion(birds);
// Weight the forces
sep.mult(1.5);
ali.mult(1.0);
coh.mult(1.0);
// Add them to acceleration
this.acceleration.add(sep);
this.acceleration.add(ali);
this.acceleration.add(coh);
}
avoidMouse(m) {
// Encourage birds to avoid the mouse cursor area
let desired = p5.Vector.sub(this.position, m);
let d = desired.mag();
if (d < 100) {
desired.setMag(this.maxSpeed);
let steer = p5.Vector.sub(desired, this.velocity);
steer.limit(this.maxForce);
this.acceleration.add(steer);
}
}
update() {
this.velocity.add(this.acceleration);
this.velocity.limit(this.maxSpeed);
this.position.add(this.velocity);
this.acceleration.mult(0);
// Wrap around edges for continuous movement
if (this.position.x < 0) this.position.x = width;
if (this.position.x > width) this.position.x = 0;
if (this.position.y < 0) this.position.y = height;
if (this.position.y > height) this.position.y = 0;
}
display() {
fill(70, 70, 70);
noStroke();
let theta = this.velocity.heading() + radians(90);
push();
translate(this.position.x, this.position.y);
rotate(theta);
beginShape();
vertex(0, -6);
vertex(-3, 3);
vertex(3, 3);
endShape(CLOSE);
pop();
}
// Boids Helper Methods:
separate(birds) {
let desiredSeparation = 25;
let steer = createVector(0,0);
let count = 0;
for (let other of birds) {
let d = p5.Vector.dist(this.position, other.position);
if ((d > 0) && (d < desiredSeparation)) {
let diff = p5.Vector.sub(this.position, other.position);
diff.normalize();
diff.div(d);
steer.add(diff);
count++;
}
}
if (count > 0) {
steer.div(count);
steer.setMag(this.maxSpeed);
steer.sub(this.velocity);
steer.limit(this.maxForce);
}
return steer;
}
align(birds) {
let neighborDist = 50;
let sum = createVector(0,0);
let count = 0;
for (let other of birds) {
let d = p5.Vector.dist(this.position, other.position);
if ((d > 0) && (d < neighborDist)) {
sum.add(other.velocity);
count++;
}
}
if (count > 0) {
sum.div(count);
sum.setMag(this.maxSpeed);
let steer = p5.Vector.sub(sum, this.velocity);
steer.limit(this.maxForce);
return steer;
} else {
return createVector(0,0);
}
}
cohesion(birds) {
let neighborDist = 50;
let sum = createVector(0,0);
let count = 0;
for (let other of birds) {
let d = p5.Vector.dist(this.position, other.position);
if ((d > 0) && (d < neighborDist)) {
sum.add(other.position);
count++;
}
}
if (count > 0) {
sum.div(count);
return this.seek(sum);
} else {
return createVector(0,0);
}
}
seek(target) {
let desired = p5.Vector.sub(target, this.position);
desired.setMag(this.maxSpeed);
let steer = p5.Vector.sub(desired, this.velocity);
steer.limit(this.maxForce);
return steer;
}
}