xxxxxxxxxx
191
/*
=======
METHOD: Initialize global constants and empty particle array
=======
*/
const PARTICLE_COUNT = 100;
const HEALTHY_PARTICLE_COUNT = 65;
const UNHEALTHY_PARTICLE_COUNT = PARTICLE_COUNT - HEALTHY_PARTICLE_COUNT;
const LOW_MOTILITY_COUNT = 20;
const MEDIUM_MOTILITY_COUNT = 30;
const HIGH_MOTILITY_COUNT = PARTICLE_COUNT - LOW_MOTILITY_COUNT - MEDIUM_MOTILITY_COUNT;
const TAIL_LENGTH = 20;
const TAIL_SCALE = .95;
let particles = [];
/*
=======
METHOD: p5.js setup function, initializes canvas and particle settings
=======
*/
function setup() {
createCanvas(400, 750).parent(createDiv().id('sketch-holder'));
resetParticles();
}
/*
=======
METHOD: // Shuffle an array in place
=======
*/
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
/*
=======
METHOD: Resets the particle array, populating it with new Particle objects
=======
*/
function resetParticles() {
particles = [];
for (let i = 0; i < PARTICLE_COUNT; i++) {
const isHealthy = i < HEALTHY_PARTICLE_COUNT;
// Determine motility based on health status
const motility = isHealthy ? "high" : "low";
particles.push(new Particle(mouseX, mouseY, isHealthy, motility));
}
}
/*
=======
METHOD: p5.js draw function, updates and draws all particles
=======
*/
function draw() {
// Set the background to blue
background("#C9E3E5");
// Sort particles by layer
particles.sort((a, b) => a.layer - b.layer);
for (const particle of particles) {
particle.update();
particle.show();
}
}
/*
=======
CLASS: Particle
- Represents a fish-like particle with various properties and behaviors.
- pos: A vector representing the particle's current position.
- isHealthy: A boolean indicating the health status of the particle.
- motility: A string determining the movement speed of the particle ('low', 'medium', or 'high').
- history: An array storing the particle's past positions for its tail.
- xoff, yoff: Random offsets used with Perlin noise for smooth movement.
- layer: Randomly assigned layer (1 or 2) for sorting or visual layering purposes.
- scaleFactor: Random factor affecting the particle's scale, influencing movement or size.
=======
*/
class Particle {
constructor(x, y, isHealthy, motility) {
this.pos = createVector(x, y);
this.isHealthy = isHealthy;
this.motility = motility;
this.history = [];
this.xoff = random(20000);
this.yoff = random(40000);
this.layer = random([1, 2]); // Assign random layer
this.scaleFactor = random(0.5, 2.0); // Add this line for unique scaling
}
/*
=======
METHOD: update
- Governs the particle's movement and history tracking.
- Movement influenced by Perlin noise, motility, and unique scaling factor.
- Position updated based on calculated noise values.
- Maintains a trail of past positions for tail visualization.
Details:
- Speed factor derived from the particle's motility.
- `xoff` and `yoff` adjusted for new noise values.
- Particle's position set based on noise and canvas dimensions.
- Current position added to `history` for tail trail.
- Ensures tail length stays consistent by trimming `history`.
=======
*/
update() {
const speedFactor = { 'low': 0.15, 'medium': 0.9, 'high': .7 }[this.motility];
this.xoff += 0.007 * speedFactor * this.scaleFactor; // Multiply with unique scaleFactor
this.yoff += 0.007 * speedFactor * this.scaleFactor; // Multiply with unique scaleFactor
this.pos.set(
map(noise(this.xoff), 0, 1, 0, width + 20),
map(noise(this.yoff), 0, 1, 0, height + 40)
);
this.history.push(this.pos.copy());
if (this.history.length > TAIL_LENGTH) {
this.history.shift();
}
}
/*
=======
METHOD: Draws the particle and its tail on the canvas
=======
*/
show() {
if (this.isHealthy) {
fill("#A4CCCE");
noStroke();
} else {
fill("#AE9A93");
noStroke();
}
// Adding the noise offset to the position for a 'sketchy' appearance
let noiseOffset = random(-0.5, 0.5); // A small random offset
for (let i = 0; i < this.history.length; i++) {
const pos = this.history[i];
ellipse(pos.x + noiseOffset, pos.y + noiseOffset, (i + 2) * TAIL_SCALE, (i + 2) * TAIL_SCALE);
}
if (this.history.length >= TAIL_LENGTH) {
const eyeOffsetX = 5; // Horizontal distance from the center of the fish to each eye
const eyeOffsetY = 3; // Vertical distance from the center of the fish to each eye
const eyeSize = 3; // Diameter of the eye
// The 'head' of the fish is the last point in the history
const head = this.history[this.history.length - 1];
if (this.isHealthy) {
// Draw eyes for healthy fish
// Draw left eye
fill(0); // Black color
ellipse(head.x - eyeOffsetX, head.y - eyeOffsetY, eyeSize);
// Draw right eye
fill(0); // Black color
ellipse(head.x + eyeOffsetX, head.y - eyeOffsetY, eyeSize);
} else {
// Draw eyes (or X marks) for unhealthy fish
// Draw left X mark
stroke("#580000"); // Black color
line(head.x - eyeOffsetX - 1.5, head.y - eyeOffsetY - 1.5, head.x - eyeOffsetX + 1.5, head.y - eyeOffsetY + 1.5);
line(head.x - eyeOffsetX + 1.5, head.y - eyeOffsetY - 1.5, head.x - eyeOffsetX - 1.5, head.y - eyeOffsetY + 1.5);
// Draw right X mark
stroke("#470000")
line(head.x + eyeOffsetX - 1.5, head.y - eyeOffsetY - 1.5, head.x + eyeOffsetX + 1.5, head.y - eyeOffsetY + 1.5);
line(head.x + eyeOffsetX + 1.5, head.y - eyeOffsetY - 1.5, head.x + eyeOffsetX - 1.5, head.y - eyeOffsetY + 1.5);
// Draw frown (using arc)
}
}
}
}