xxxxxxxxxx
150
let rivers = [];
function setup() {
createCanvas(600, 400);
generateRivers();
}
function draw() {
background(220);
for (let river of rivers) {
river.tick(); // Update and animate the particles
river.display();
}
}
function mousePressed() {
generateRivers();
}
function generateRivers() {
rivers = [];
let numRivers = random(3, 6); // Generate multiple rivers
for (let i = 0; i < numRivers; i++) {
let startVec = createVector(-10, random(-10, height+10))
let endVec = createVector(width+10, random(-10, height+10,))
let seed = random(1000); // Seed for perlin noise
let meanderStrength = random(4, 5); // Meander strength for larger curves
let minwidth = 3;
let maxwidth = 5;
let riverColor = color(random(0, 100), random(100, 200), random(200, 255));
let river = new River(seed, startVec, endVec, meanderStrength, minwidth, maxwidth, riverColor);
rivers.push(river);
}
}
class River {
constructor(seed, startVec, endVec, meanderStrength, minwidth, maxwidth, riverColor) {
this.seed = seed;
this.startVec = startVec;
this.endVec = endVec;
this.meanderStrength = meanderStrength;
this.minwidth = minwidth;
this.maxwidth = maxwidth;
this.riverColor = riverColor;
this.points = [];
this.particles = [];
this.generatePath();
this.initParticles();
}
generatePath() {
noiseSeed(this.seed);
let numPoints = 100; // Number of points to generate along the path
let tStep = 1 / numPoints;
this.points.push(this.startVec.copy()); // Ensure startVec is the first point
for (let t = tStep; t <= 1; t += tStep) {
let x = lerp(this.startVec.x, this.endVec.x, t);
let y = lerp(this.startVec.y, this.endVec.y, t);
// Add noise-based meandering
let noiseFactor = noise(t * this.meanderStrength) - 0.5;
let angle = noiseFactor * PI; // Angle for swaying left or right
let offset = p5.Vector.fromAngle(angle).mult(this.meanderStrength * 20); // Sway amount
let nextPoint = createVector(x + offset.x, y + offset.y);
this.points.push(nextPoint);
}
this.points.push(this.endVec.copy()); // Ensure endVec is the last point
// Smooth the path using a Catmull-Rom spline
this.points = this.smoothPath(this.points);
}
// Helper function to smooth the path using Catmull-Rom spline interpolation
smoothPath(pts) {
let smoothPts = [];
let granularity = 0.05; // How fine the smoothing is
for (let i = 0; i < pts.length - 1; i++) {
for (let t = 0; t <= 1; t += granularity) {
let smoothedPt = this.catmullRom(pts[i - 1] || pts[i], pts[i], pts[i + 1], pts[i + 2] || pts[i + 1], t);
smoothPts.push(smoothedPt);
}
}
return smoothPts;
}
// Catmull-Rom interpolation
catmullRom(p0, p1, p2, p3, t) {
let x = 0.5 * ((2 * p1.x) + (-p0.x + p2.x) * t + (2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * t * t + (-p0.x + 3 * p1.x - 3 * p2.x + p3.x) * t * t * t);
let y = 0.5 * ((2 * p1.y) + (-p0.y + p2.y) * t + (2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * t * t + (-p0.y + 3 * p1.y - 3 * p2.y + p3.y) * t * t * t);
return createVector(x, y);
}
initParticles() {
this.particles = []; // Clear any old particles
let particleReleaseRate = 10; // Release a new particle every 10 frames
for (let i = 0; i < this.points.length; i += particleReleaseRate) {
let particle = {
pos: this.points[i].copy(),
speed: random(1, 2), // Random particle speed
color: color(this.riverColor.levels[0] + random(-20, 20), this.riverColor.levels[1] + random(-20, 20), this.riverColor.levels[2] + random(-20, 20), 150),
pointIndex: i // Start the particle at its current point index
};
this.particles.push(particle);
}
}
tick() {
for (let particle of this.particles) {
if (particle.pointIndex < this.points.length - 1) {
let nextPoint = this.points[particle.pointIndex];
let dir = p5.Vector.sub(nextPoint, particle.pos).normalize().mult(particle.speed);
particle.pos.add(dir);
// Move to the next point in the path if close enough
if (p5.Vector.dist(particle.pos, nextPoint) < 2) {
particle.pointIndex++;
}
} else {
// Reset particle to the start to create continuous flow
particle.pos = this.points[0].copy();
particle.pointIndex = 0;
}
}
}
display() {
noFill();
strokeWeight(3);
beginShape();
for (let i = 0; i < this.points.length; i++) {
let w = map(i, 0, this.points.length, this.maxwidth, this.minwidth); // Vary width along path
stroke(this.riverColor);
strokeWeight(w);
vertex(this.points[i].x, this.points[i].y);
}
endShape();
// Draw particles
noStroke();
for (let particle of this.particles) {
fill(particle.color);
ellipse(particle.pos.x, particle.pos.y, 5, 5);
}
}
}