xxxxxxxxxx
317
// Pre-setup pi
const INIT_PI = 3.14159265358979323846;
// Dimensions of the image to be rendered
let imageWidth = 0;
let imageHeight = 0;
// Mouse click radius
let clickRadius = 15;
let radiusPrompt;
// The number of particles spawned per click
const particlesPerClick = 1000;
// The maximum number of particles that can be spawned
const maxParticles = 100000;
// Diffusion size
const diffK = 1;
// Decay factor
let decayT = 0.9;
let decayPrompt;
// Sensor angle (radians)
let SA = INIT_PI / 8;
let SAprompt;
// Rotate angle (radians)
let RA = INIT_PI / 4;
let RAprompt;
// Sensor offset distance
const SO = 9;
// Sensor width
const SW = 1;
// Step size
let SS = 1;
let SSprompt;
// Attract factor
const depT = 10;
//random densit
const randomDensity = 0.2;
// Arrays containing the particles, attracters, and emitters in the scene
let particles = [];
let attracters = [];
let emitters = [];
// Number of particles emitted per second by emitters in the scene
let numParticlesEmitted = 5;
// Image of attractors (the visual output by p5)
let at;
let updateButton;
let user_wanted_r = 0;
let user_wanted_g = 0;
let user_wanted_b = 0;
function setup() {
imageWidth = 1275;
imageHeight = 500;
at = createImage(imageWidth, imageHeight);
at.loadPixels();
let radiusText = createElement("rad", "r ");
radiusPrompt = createInput(str(clickRadius));
let decayText = createElement("dec", " decayT ");
decayPrompt = createInput(str(decayT));
// let SAText = createElement("sa", " SA ");
// SAprompt = createInput(str((SA / PI) * 180));
// let RAText = createElement("ra", " RA ");
// RAprompt = createInput(str(str((RA / PI) * 180)));
// let SSText = createElement("ss", " SS ");
// SSprompt = createInput(str(SS));
decayPrompt.size(40);
radiusPrompt.size(40);
// SAprompt.size(40);
// RAprompt.size(40);
// SSprompt.size(40);
updateButton = createButton("UPDATE");
updateButton.mousePressed(updateValues);
updateButton.parent("clearNrand");
radiusPrompt.parent("sliders");
decayPrompt.parent("sliders");
// SAprompt.parent("sliders");
// RAprompt.parent("sliders");
// SSprompt.parent("sliders");
radiusText.parent("sliderLabels");
decayText.parent("sliderLabels");
// SAText.parent("sliderLabels");
// RAText.parent("sliderLabels");
// SSText.parent("sliderLabels");
let clearButton = createButton("CLEAR");
clearButton.mousePressed(clearGrid);
clearButton.parent("clearNrand");
let randomButton = createButton("RANDOM");
randomButton.mousePressed(randomSpread);
randomButton.parent("clearNrand");
updateButton.parent("clearNrand");
canvas = createCanvas(imageWidth, imageHeight);
canvas.mouseClicked(canvasClick);
canvas.parent("slimeContainer");
background(0);
for (let i = 0; i < imageWidth; i++) {
let length = [];
for (let j = 0; j < imageHeight; j++) {
length.push(0);
}
attracters.push(length);
}
}
function draw() {
// Main draw loop: handles drawing particles, sensing, and decay
background(0);
stroke(255, 165, 0);
strokeWeight(5);
fill(255, 0, 0, 50);
textAlign(CENTER, CENTER);
image(at, 0, 0);
for (let x of emitters) {
for (
let i = 0;
i < numParticlesEmitted && particles.length < maxParticles;
i++
) {
particles.push([x[0], x[1], TWO_PI * Math.random()]);
}
point(x[0], x[1]);
}
stroke(255, 0, 0, 0);
// Particle and mouse interaction visuals
ellipse(mouseX, mouseY, 2 * clickRadius, 2 * clickRadius);
stroke(user_wanted_r, user_wanted_g, user_wanted_b);
strokeWeight(1);
for (let xs of particles) {
point(xs[0], xs[1]);
}
sense();
updateParticles();
decay();
// Key controls for changing particle visual characteristics
if (keyIsDown(UP_ARROW)) {
user_wanted_r = Math.min(user_wanted_r + 10, 255);
user_wanted_g = Math.min(user_wanted_g + 10, 255);
user_wanted_b = Math.min(user_wanted_b + 10, 255);
} else if (keyIsDown(DOWN_ARROW)) {
user_wanted_r = Math.max(user_wanted_r - 10, 0);
user_wanted_g = Math.max(user_wanted_g - 10, 0);
user_wanted_b = Math.max(user_wanted_b - 10, 0);
}
}
function clearGrid() {
// Clear all particles and emitters from the canvas
particles = [];
emitters = [];
}
function randomSpread() {
// Randomly distribute particles across the canvas
clearGrid();
for (let i = 0; i < imageWidth * imageHeight * randomDensity; i++) {
particles.push([
Math.random() * imageWidth,
Math.random() * imageHeight,
TWO_PI * Math.random(),
]);
}
}
function updateValues() {
// Update interaction parameters based on user input
clickRadius = parseInt(radiusPrompt.value());
decayT = parseFloat(decayPrompt.value());
// SA = parseFloat((SAprompt.value() / 180) * PI);
// RA = parseFloat((RAprompt.value() / 180) * PI);
// SS = parseFloat(SSprompt.value());
}
function writePixel(image, x, y, g) {
// Write a pixel to the image at the specified coordinates
let index = (x + y * imageWidth) * 4;
image.pixels[index] = g;
image.pixels[index + 1] = g;
image.pixels[index + 2] = 0;
image.pixels[index + 3] = 255;
}
function modifiedRound(i, axis) {
// Custom rounding function to handle canvas edges
let x = round(i);
if (x < 0 && axis == "x") x += imageWidth;
else if (x >= imageWidth && axis == "x") x %= imageWidth;
else if (x < 0 && axis == "y") x += imageHeight;
else if (x >= imageHeight && axis == "y") x %= imageHeight;
return x;
}
function sense() {
// The sense function is responsible for the decision-making process of each particle.
// - It uses three sensors (left, center, right) to detect the environment ahead of the particle.
// - Based on the sensor readings, the particle decides whether to continue straight, turn left, or turn right.
// - This decision influences the particle's path, creating intricate patterns on the canvas as particles avoid their own trails.
for (let i = 0; i < particles.length; i++) {
let options = [0, 0, 0];
options[1] =
attracters[
modifiedRound(particles[i][0] + SO * cos(particles[i][2]), "x")
][modifiedRound(particles[i][1] + SO * sin(particles[i][2]), "y")];
options[0] =
attracters[
modifiedRound(particles[i][0] + SO * cos(particles[i][2] + SA), "x")
][modifiedRound(particles[i][1] + SO * sin(particles[i][2] + SA), "y")];
options[2] =
attracters[
modifiedRound(particles[i][0] + SO * cos(particles[i][2] - SA), "x")
][modifiedRound(particles[i][1] + SO * sin(particles[i][2] - SA), "y")];
if (options[1] >= options[2] && options[1] >= options[0]) {
continue;
} else if (options[0] > options[2]) {
particles[i][2] = (particles[i][2] + RA) % TWO_PI;
} else if (options[0] < options[2]) {
particles[i][2] = (particles[i][2] - RA) % TWO_PI;
} else {
let rand = Math.random();
if (rand < 0.5) {
particles[i][2] = (particles[i][2] + RA) % TWO_PI;
} else {
particles[i][2] = (particles[i][2] - RA) % TWO_PI;
}
}
}
}
function updateParticles() {
// This function updates the position and state of each particle in the simulation.
// - It iterates through all particles, moving them based on their current direction (heading).
// - The function handles wrapping around the edges of the canvas to maintain continuous flow.
// - For each movement, the particle deposits a trail on the attracters grid, which is used for sensing and visual output.
for (let i = 0; i < particles.length; i++) {
let x = (particles[i][0] + SS * cos(particles[i][2])) % imageWidth;
let y = (particles[i][1] + SS * sin(particles[i][2])) % imageHeight;
if (x < 0) {
x += imageWidth;
}
if (y < 0) {
y += imageHeight;
}
particles[i][0] = x;
particles[i][1] = y;
let p1 = modifiedRound(particles[i][0], "x");
let p2 = modifiedRound(particles[i][1], "y");
attracters[p1][p2] += depT;
}
}
function decay() {
// This function manages the decay and diffusion of particle trails.
// It updates the visual representation of each particle's trail on the canvas.
// - First, it iterates over the canvas, applying the decay factor to reduce the brightness of trails.
// - Then, it applies a blur filter (simulating diffusion) to create a smoother visual effect.
// - Finally, the attracters array is updated based on the decayed and diffused pixel values.
for (let i = 0; i < imageWidth; i++) {
for (let j = 0; j < imageHeight; j++) {
writePixel(at, i, j, attracters[i][j]);
}
}
at.filter(BLUR, diffK);
for (let i = 0; i < imageWidth; i++) {
for (let j = 0; j < imageHeight; j++) {
attracters[i][j] = at.pixels[(i + j * imageWidth) * 4] * decayT;
}
}
at.updatePixels();
}
function canvasClick() {
// This function handles user interactions with the canvas.
// - If the SHIFT key is held down while clicking, it removes particles within the click radius.
// - If the ENTER key is held, it adds a new emitter at the mouse location.
// - Otherwise, it spawns new particles around the click location within the specified radius.
// Each particle is initialized with a random direction.
if (keyIsDown(SHIFT)) {
const notRemoved = [];
for (let xs of particles) {
if (
Math.sqrt((mouseX - xs[0]) ** 2 + (mouseY - xs[1]) ** 2) > clickRadius
) {
notRemoved.push(xs);
}
}
particles = notRemoved;
} else if (keyIsDown(ENTER)) {
emitters.push([mouseX, mouseY]);
} else {
for (
let i = 0;
i < particlesPerClick && particles.length < maxParticles;
i++
) {
let dis = clickRadius * Math.random();
let ang = TWO_PI * Math.random();
let x = mouseX + dis * cos(ang);
let y = mouseY + dis * sin(ang);
particles.push([x, y, TWO_PI * Math.random()]);
}
}
}