xxxxxxxxxx
819
// ONLINE EXHIBITION PCD@PORTO 2024 - THEME: LUDIC AESTHETICS
// code created by Vadym Alyekseyenko
// More works: www.alyekseyenko.site
var colors = [];
let oswaldFont; // Declare a variable to store the loaded font
for (let i = 0; i < 500; i++) {
colors.push({
r: Math.floor(Math.random() * 256),
g: Math.floor(Math.random() * 256),
b: Math.floor(Math.random() * 256),
});
}
let saveImageAfterFrames = 2; // Número de frames após a geração completa para salvar a imagem
let saveImageTimer = 0; // Timer para controlar quando salvar a imagem
let generationTimeout; // Declare a variable to store the timeout ID
let targetLineLength = 1000; // Change the value as needed
let normalizedPositionAlongLine = 0;
let winnerInfo = {
name: "",
area: 0,
capital: "",
population: 0,
latlng: [],
color: null, // Add color property to store the winner's color
};
let imageSaved = false; // Adicione uma variável para controlar se a imagem já foi salva
// Declare the variable in the global scope
let imageSavingInProgress = false;
let angleNoiseScale = 2.001; // ou qualquer valor desejado
let angleNoiseScale1 = 2.001;
let angleNoiseScale2 = 2.002;
let angleNoiseScale3 = 2.003;
let winnerName = "";
let longestLineAgent;
let generationStarted = false;
let totalHeight;
var vectorFieldScale = 10000; // Experiment with different values
let startColor, endColor;
let lastGenerationTime;
var lineGenerationPercentage = 0.15;
let minStroke = 0.5; // You can set the value according to your requirements
let maxStroke = 2; // You can set the value according to your requirements
var nAgents = 70; // Increase the number of agents
let border = 0;
let agents = [];
let selectedCountries = [];
let allCountries = []; // New array to store all available countries
let remainingCountries = []; // New array to track remaining countries
let flagRegionY;
let timeBetweenFrames;
let savingTriggered = false;
function preload() {
// Load Oswald font from the root folder
oswaldFont = loadFont('Oswald-Bold.ttf'); // Replace 'oswald.ttf' with the actual font file name
}
function setup() {
createCanvas(3500, 5000);
colorMode(HSB, 360, 100, 100);
rectMode(CENTER);
strokeCap(SQUARE);
background(0, 0, 0);
// Set the loaded font as the default font for text elements
textFont(oswaldFont);
frameMain = Math.floor(random(500, 6000));
lastGenerationTime = millis(); // Initialize lastGenerationTime
// Reset the allCountries array each time the sketch is run
allCountries = [];
timeBetweenFrames = Math.floor(random(500, 6000));
remainingCountries = []; // Initialize remainingCountries array
// Make API call to get all country information
fetch("https://restcountries.com/v2/all")
.then((response) => response.json())
.then((data) => {
// Store all countries in the allCountries array
allCountries = [data];
remainingCountries = [data]; // Initialize remainingCountries
// Select nAgents random countries without repetition
for (let i = 0; i < nAgents; i++) {
const randomIndex = Math.floor(Math.random() * remainingCountries.length);
const selectedCountry = remainingCountries.splice(randomIndex, 1)[0];
selectedCountries.push(selectedCountry);
}
// Log the selected countries
console.log("Selected Countries:", selectedCountries);
// Initialize agents after fetching country data
for (let i = 0; i < nAgents; i++) {
agents.push(new Agent(random(width), random(height), [selectedCountries[i]]));
}
longestLineAgent = agents[0];
})
.catch((error) => console.error("Error fetching country data:", error));
// Initialize flagRegionY based on canvas height
flagRegionY = height * 0.5;
// Calculate totalHeight based on the content of the canvas
totalHeight = height - border * 2;
}
function draw() {
if (frameCount > frameMain + 1) {
noLoop(); // Stop the draw loop after 1500 frames
}
// Draw all country flags after frame x
drawAllCountryFlags();
// Draw the border (should be drawn on top of everything)
stroke(0);
strokeWeight(150);
noFill();
rect(width / 2, height / 2, width, height);
// Draw the footer (should be drawn on top of everything)
drawFooter();
// Draw the winner information
if (frameCount > frameMain - 2) {
if (longestLineAgent) {
longestLineAgent.displayWinnerInfo();
}
}
// Display the header
drawHeader();
function drawAllCountryFlags() {
// Draw all country flags after frame x
if (frameCount >= 50) {
for (let i = 0; i < agents.length; i++) {
agents[i].drawCountryFlag();
}
}
}
function drawFooter() {
// Draw a black background for the footer
fill(0);
noStroke();
rect(width / 2, height - 100, width, 600);
}
// Declare startColor and endColor here
startColor = color(255, 0, 0); // Set your desired start color
endColor = color(0, 0, 255); // Set your desired end color
// Display list of countries with squares
displayCountryList();
// Always update the winner information based on the longest drawn line
if (agents.length > 0) {
longestLineAgent = agents.reduce((a, b) =>
a.drawnLineLength > b.drawnLineLength ? a : b
);
//console.log("Winner found:", longestLineAgent.selectedCountry); // Add this line for debugging
winnerInfo = {
name: longestLineAgent.selectedCountry.name,
area: longestLineAgent.selectedCountry.area,
capital: longestLineAgent.selectedCountry.capital,
population: longestLineAgent.selectedCountry.population,
latlng: longestLineAgent.selectedCountry.latlng,
};
// Set the winner's name
winnerName = winnerInfo.name;
}
// Draw all country flags after frame 100
if (frameCount >= 100 && frameCount < frameMain) {
for (let i = 0; i < agents.length; i++) {
agents[i].loadFlagImage();
}
}
// Draw lines within the canvas, excluding the border
for (let i = 0; i < agents.length; i++) {
if (
frameCount <= frameMain ||
(agents[i].p.x > border &&
agents[i].p.x < width - border &&
agents[i].p.y > border &&
agents[i].p.y < height - border)
) {
agents[i].update();
agents[i].drawCountryFlag();
// Check the condition for drawing the flag and country information
// Remove the condition related to flagRegionY
if (agents[i].drawnLineLength >= targetLineLength && !agents[i].flagDrawn) {
agents[i].drawCountryFlag();
}
}
}
// Draw all country flags after line generation is completed
if (frameCount >= frameMain) {
for (let i = 0; i < agents.length; i++) {
agents[i].drawCountryFlag();
}
}
// Display the winner information
if (frameCount > frameMain - 2) {
if (longestLineAgent) {
longestLineAgent.displayWinnerInfo();
}
}
// Draw all country flags after frame 50
if (frameCount >= 50) {
displayAllCountryFlags();
}
let generationDelay;
let generationTimeout;
let generationCompleted = false;
let savingTriggered = false;
// Check if it's time to generate a new art
if (millis() - lastGenerationTime > generationDelay && !generationStarted) {
generationStarted = true;
// Reset and regenerate the art
lastGenerationTime = millis();
generationStarted = false; // Reset to allow the next generation
// Set generationTimeout to reset the canvas after the specified frame count
generationTimeout = setTimeout(function () {
// Reset the canvas after the specified frame count
resetCanvas();
}, frameMain);
}
// Check if the generation is complete based on the frame count
if (frameCount >= frameMain + 1) {
// Clear the previous timeout
clearTimeout(generationTimeout);
// Set savingTriggered flag to true to prevent multiple saves
savingTriggered = true;
if (!imageSavingInProgress) {
imageSavingInProgress = true;
// Draw the header on the canvas (including winner information)
drawHeader();
// Append a timestamp to make the filename unique
let timestamp = new Date().getTime();
// Automatically save the canvas as an HD JPG image
let min = -999999999999;
let max = 999999999999;
let randomNumber = Math.floor(Math.random() * (max - min + 1) + min);
// Save the image here
saveCanvas("WF THE WINNER IS - " + winnerName + " - HUMANITY PCD@PORTO 2024 - BY VADYM ALYEKSEYENKO - ID:" + randomNumber + " - " + timestamp + " - FrameMain:" + frameMain, "jpg");
// Set imageSaved to true after saving
imageSaved = true;
imageSavingInProgress = false;
// Reset the canvas
resetCanvas();
}
}
}
function drawHeader() {
// Draw the header on the canvas (including winner information)
// This function should include the relevant text or graphic
// For example:
fill(255);
textSize(36);
textAlign(CENTER, CENTER);
noStroke();
if (frameCount <= frameMain-1) {
// Keep this part as it is for the initial phase
text("HUMANITY by Vadym Alyekseyenko", width / 2, 30);
} else {
// Display winner information in the header after the generation is complete
let winnerText = "Current Winner: ";
if (winnerInfo.name !== "") {
winnerText += `${winnerInfo.name} (${winnerInfo.area} units)`;
} else {
winnerText += "No winner yet";
}
text(winnerText, width / 2, 30);
}
}
function resetCanvas() {
background(0);
agents = [];
selectedCountries = [];
remainingCountries = [allCountries];
longestLineAgent = null;
winnerName = "";
generationStarted = false;
// Select new random countries
for (let i = 0; i < nAgents; i++) {
const randomIndex = Math.floor(Math.random() * remainingCountries.length);
const selectedCountry = remainingCountries.splice(randomIndex, 1)[0];
selectedCountries.push(selectedCountry);
}
// Initialize agents after fetching new country data
for (let i = 0; i < nAgents; i++) {
agents.push(new Agent(random(width), random(height), [selectedCountries[i]]));
}
longestLineAgent = agents[0];
// Reset other necessary variables
frameMain = Math.floor(random(500, 6000));
totalHeight = height - border * 2;
vectorFieldScale = 10000;
lastGenerationTime = millis();
lineGenerationPercentage = 0.15;
minStroke = 1;
maxStroke = 5;
flagRegionY = height * 0.5;
angleNoiseScale = 2.001;
angleNoiseScale1 = 2.001;
angleNoiseScale2 = 2.002;
angleNoiseScale3 = 2.003;
startColor = color(255, 0, 0);
endColor = color(0, 0, 255);
frameCount = 0;
// Restart the drawing loop
//redraw(); // Redraw the canvas immediately
}
// Function to display all generated country flags on the right and left sides
function displayAllCountryFlags() {
const flagSize = 50;
const margin = 10;
const spacing = 50; // Added spacing between flags
const flagsPerSide = 35;
// Calculate the total height of all flags
const totalHeight = (flagSize + margin + spacing) * flagsPerSide - margin;
// Calculate the starting Y position to center the flags vertically
let startY = (height - totalHeight) / 2 + flagSize / 2;
// Calculate the starting Y position for the left side
let startYLeft = startY;
// Calculate the starting X position for the left side
let startXLeft = margin + flagSize / 2;
// Calculate the starting X position for the right side
let startXRight = width - margin - flagSize / 2;
// Iterate through selectedCountries and find the corresponding agent
for (let i = 0; i < selectedCountries.length; i++) {
const country = selectedCountries[i];
// Find the agent corresponding to the current country
const agent = agents.find((a) => a.selectedCountry === country);
if (agent && agent.flagImage) {
// Check if agent and flagImage exist
const flagWidth = 50;
const flagHeight =
(agent.flagImage.height / agent.flagImage.width) * flagWidth;
// Determine if the flag should be displayed on the right or left
const side = i < flagsPerSide ? "right" : "left";
// Set the X and Y positions based on the side
let currentX, currentY;
if (side === "right") {
currentX = startXRight;
} else {
currentX = startXLeft;
}
currentY = side === "right" ? startY : startYLeft;
// Draw the black background for the flag
fill(0);
noStroke();
rect(currentX, currentY, flagSize, flagSize);
// Draw the flag on the black background
imageMode(CENTER);
image(agent.flagImage, currentX, currentY, flagWidth, flagHeight);
// Update the Y position for the next flag on the corresponding side
if (side === "right") {
startY += flagHeight + margin + spacing;
} else {
startYLeft += flagHeight + margin + spacing;
}
}
}
}
// Draw all country flags after frame x
function drawAllCountryFlags() {
const startDrawingFlagsFrame = 200; // Change x to the desired frame number
if (frameCount >= startDrawingFlagsFrame) {
for (let i = 0; i < agents.length; i++) {
const agent = agents[i];
// Check if the agent's Y position has crossed the flagRegionY
if (agent.p.y > flagRegionY) {
agent.drawCountryFlag();
}
}
}
}
appliedNoise = true;
function applyNoiseEffect() {
try {
let buffer = createGraphics(3500, 5000); // Create an off-screen buffer with the canvas size
buffer.background(0); // Set the buffer background to black
buffer.loadPixels();
for (let x = 0; x < 3500; x++) {
for (let y = 0; y < 5000; y++) {
let index = (x + y * 3500) * 4;
let bright =
(red(get(x, y)) + green(get(x, y)) + blue(get(x, y))) / 3;
// Add grain to the brightness
bright += random(-10, 10);
buffer.pixels[index] = buffer.pixels[index + 1] = buffer.pixels[
index + 2
] = bright;
buffer.pixels[index + 3] = 255; // Set alpha to 255
}
}
buffer.updatePixels();
// Draw the buffer onto the main canvas, centered
imageMode(CENTER);
image(buffer, width / 2, height / 2);
} catch (error) {
console.error("Error in applyNoiseEffect:", error);
}
}
function keyPressed() {
if (key === "N" || key === "n") {
console.log("N key pressed");
// Press 'N' to apply the noise effect after the generation is done
applyNoiseEffect();
} else if (key === "S" || key === "s") {
console.log("S key pressed");
// Press 'S' or 's' to save the canvas as an HD JPG image
saveCanvas("THE WINNER IS - " + winnerName + " - " + "HUMANITY-ONLINE EXHIBITION PCD@PORTO 2024 - BY VADYM ALYEKSEYENKO", "jpg");
}
}
// Function to display the list of selected countries
function displayCountryList() {
const iconSize = 20; // Icon size
const spacing = 25; // Spacing between squares and text
// Draw a black background for the footer
fill(0);
noStroke();
rect(width / 2, height - 100, width, 600);
let totalWidth = selectedCountries.length * (iconSize + spacing) - spacing;
let startX = width / 2 - totalWidth / 2;
let startY = height - 70;
for (let i = 0; i < selectedCountries.length; i++) {
const country = selectedCountries[i];
// Modify the stroke color based on the country's properties
let countryColor = generateColorForCountry(country);
stroke(countryColor);
// Display country name with specific line color and square
fill(countryColor);
noStroke();
rectMode(CENTER);
rect(startX + iconSize / 2, startY, iconSize, iconSize);
fill(255);
textSize(12);
textAlign(LEFT, CENTER);
push();
translate(startX + iconSize / 2, height - 70);
rotate(-HALF_PI);
text(country.name, 20, 0);
pop();
startX += iconSize + spacing;
}
}
// Function to generate color for a country based on its properties
function generateColorForCountry(country) {
let colorSeed = 0;
for (let i = 0; i < country.name.length; i++) {
colorSeed += country.name.charCodeAt(i);
}
// Colored Art
const selectedColor = colors[colorSeed % colors.length];
// Ensure a minimum brightness level (adjust the value as needed)
const minBrightness = 40;
const brightness = (selectedColor.r + selectedColor.g + selectedColor.b) / 3;
if (brightness < minBrightness) {
// Adjust the brightness to meet the minimum requirement
const brightnessDifference = minBrightness - brightness;
selectedColor.r += brightnessDifference;
selectedColor.g += brightnessDifference;
selectedColor.b += brightnessDifference;
}
return color(selectedColor.r, selectedColor.g, selectedColor.b);
}
// Agent class representing drawing agents
class Agent {
constructor(x0, y0, selectedCountries) {
this.p = createVector(x0, y0);
this.pOld = createVector(this.p.x, this.p.y);
this.direction = createVector(random(-1, 1), random(-1, 1)).normalize();
this.selectedCountry =
selectedCountries[Math.floor(random(selectedCountries.length))];
this.startingCountry = this.selectedCountry;
this.scale = 5;
this.step = 3;
this.timeOffsetX += 0.005; // Adjust the time offset increment
this.timeOffsetY += 0.005; // Adjust the time offset increment
this.maxStroke = 52;
this.strokeWidth = this.maxStroke;
this.strokeChangeTime = 0;
this.strokeChangeInterval = random(50, 100);
this.color = generateColorForCountry(this.selectedCountry);
this.drawnRectangle = false;
this.flagImage = null;
this.loadFlagImage(this.startingCountry); // Load the flag of the starting country
this.flagDrawn = false;
this.shouldDrawFlag = false;
this.drawnLineLength = 0; // Initialize the drawn line length
// Add a property for constant stroke size
this.constantStrokeSize = 5; // Set the desired constant stroke size
}
// Load the country flag image
loadFlagImage(country) {
if (country && this.flagImage === null) {
const countryCode = country.alpha2Code.toLowerCase();
const flagUrl = `https://flagcdn.com/w320/${countryCode}.png`;
loadImage(flagUrl, (img) => {
this.flagImage = img;
this.shouldDrawFlag = true; // Set the flag drawing flag to true
});
}
}
drawCountryFlag() {
// Check if frameCount is greater than or equal to frameMain and the flag has not been drawn yet
if (frameCount >= frameMain && this.flagImage && this.shouldDrawFlag && !this.flagDrawn) {
const flagWidth = 50;
const flagHeight = (this.flagImage.height / this.flagImage.width) * flagWidth;
// Verificação de limite do canvas
if (
this.p.x > border &&
this.p.x < width - border &&
this.p.y > border &&
this.p.y < height - border
) {
// Verificação para evitar sobreposição ao footer
if (this.p.y + flagHeight / 2 + 5 < height - 100) {
noStroke();
imageMode(CENTER);
image(this.flagImage, this.p.x, this.p.y, flagWidth, flagHeight);
// Set flagDrawn to true after drawing the country flag
this.flagDrawn = true;
// Display additional country information
fill(255);
textSize(12);
textAlign(CENTER, TOP);
let infoText = `${this.selectedCountry.name}\nPopulation: ${this.selectedCountry.population}\nArea: ${this.selectedCountry.area} sq km\nCapital: ${this.selectedCountry.capital}`;
text(infoText, this.p.x, this.p.y + flagHeight / 2 + 5);
}
}
}
}
// Draw the starting rectangle for the agent
drawStartingRectangle() {
const ellipseSize = 20;
const padding = 20;
fill(this.color);
noStroke();
rectMode(CENTER);
ellipse(this.p.x, this.p.y, ellipseSize, ellipseSize); // Change circle to ellipse
fill(255);
textSize(25);
textAlign(CENTER, TOP);
// Adjust the text position to be centered at the bottom with padding
text(this.selectedCountry.name, this.p.x, this.p.y + ellipseSize / 2 + padding);
}
// Declare and initialize normalizedPositionAlongLine
update() {
if (!this.drawnRectangle) {
this.drawStartingRectangle();
this.drawnRectangle = true;
}
// Dentro da função update, antes de aplicar o campo de vetores
this.direction = createVector(random(-1, 1), random(-1, 1)).normalize();
// Declare populationFactor and areaFactor at the beginning of the update function
let populationFactor = map(
this.selectedCountry.population,
0,
1393409038,
1,
0
);
let areaFactor = map(this.selectedCountry.area, 0, 170982000, 0.07, 2);
// Set the value for normalizedPositionAlongLine
// Move this line to the top of the update method
normalizedPositionAlongLine = map(this.drawnLineLength, 0, targetLineLength, 0, 1);
// Create a gradient stroke
if (startColor && endColor) {
let gradientColor = lerpColor(startColor, endColor, normalizedPositionAlongLine);
stroke(gradientColor);
}
this.drawnLineLength += dist(this.pOld.x, this.pOld.y, this.p.x, this.p.y);
// Set values for startColor and endColor somewhere in your code
startColor = color(255, 0, 0); // Set your desired start color
endColor = color(0, 0, 255); // Set your desired end color
// Draw the country flag if the flag should be drawn
if (this.shouldDrawFlag && !this.flagDrawn) {
this.drawCountryFlag();
}
// Check if the flag has been drawn, and if so, stop further line generation
if (this.flagDrawn) {
return; // Exit the update function
}
// Set the value for normalizedPositionAlongLine
normalizedPositionAlongLine = map(this.drawnLineLength, 0, targetLineLength, 0, 1);
let noiseX = noise(this.timeOffsetX);
let noiseY = noise(this.timeOffsetY);
// Ensure noiseX and noiseY are defined and finite
if (isNaN(noiseX) || !isFinite(noiseX)) {
noiseX = 0;
}
if (isNaN(noiseY) || !isFinite(noiseY)) {
noiseY = 0;
}
let areaSpeedMultiplier = map(
this.selectedCountry.area,
0,
170982000,
0.1,
2
);
this.direction = createVector(
map(noiseX, 0, 10, -10, 10),
map(noiseY, 0, 10, -10, 10)
).normalize();
let vectorField = vector_field(this.p.x, this.p.y, this.scale);
if (
vectorField &&
vectorField instanceof p5.Vector &&
isFinite(vectorField.x) &&
isFinite(vectorField.y)
) {
// Draw a random vector line based on the vector field
let angleVariability = map(noise(this.p.x / 200, this.p.y / 200), 0, 1, 0, PI * 4);
let randomAngle = random(-angleVariability, angleVariability);
let randomLength = random(100); // Adjust the maximum length of the random vector
let randomVector = p5.Vector.fromAngle(randomAngle);
randomVector.mult(randomLength);
// Update position
this.p.add(randomVector);
// Verifica se o agente está próximo da borda superior ou inferior
if (this.p.y < border || this.p.y > height - border) {
// Envolve na borda superior ou inferior
this.p.y = random(border, height - border);
// Gera um novo ângulo aleatório
this.direction = createVector(random(-1, 1), random(-1, 1)).normalize();
}
// Verifica se o agente está próximo da borda esquerda ou direita
if (this.p.x < border || this.p.x > width - border) {
// Envolve na borda esquerda ou direita
this.p.x = random(border, width - border);
// Gera um novo ângulo aleatório
this.direction = createVector(random(-1, 1), random(-1, 1)).normalize();
}
let dynamicStrokeWeight = map(
this.selectedCountry.population * areaSpeedMultiplier,
0,
1393409038 * 170982000,
minStroke,
maxStroke
);
// Adjust the multipliers to increase the impact of population and area
// on the stroke weight
dynamicStrokeWeight *= 0.05; // Adjust this multiplier
dynamicStrokeWeight += populationFactor * 1.4; // Adjust this multiplier
// Use the minimum of the constant and dynamic stroke weights
strokeWeight(Math.min(this.constantStrokeSize, dynamicStrokeWeight));
// Create a gradient stroke
let gradientColor = lerpColor(startColor, endColor, normalizedPositionAlongLine);
stroke(gradientColor);
stroke(this.color);
// Draw the random vector line
line(this.pOld.x, this.pOld.y, this.p.x, this.p.y);
} else {
console.error("Invalid vectorField:", vectorField);
}
this.pOld.set(this.p.x, this.p.y);
this.timeOffsetX += 2.01;
this.timeOffsetY += 2.01;
}
displayWinnerInfo() {
if (winnerInfo && winnerInfo.name !== "") {
fill(255);
textSize(36);
textAlign(CENTER, CENTER);
fill(this.color); // Use color from the agent
textSize(18);
textAlign(CENTER, CENTER);
}
}
}
function vector_field(x, y, scale) {
// Define the base angles using Perlin noise
let angle1 = map(noise(x / scale, y / scale), 0, 1, 0, TWO_PI);
let angle2 = map(noise(x / scale + PI, y / scale + 10), 0, 1, 0, TWO_PI);
let angle3 = map(noise(x / scale + TWO_PI, y / scale + 10), 0, 1, 0, TWO_PI);
let angle4 = map(noise(x / scale + TWO_PI, y / scale + 10), 0, 1, 0, TWO_PI);
let angle5 = map(noise(x / scale + PI, y / scale + 20), 0, 1, 0, TWO_PI);
// Add random components to the angles for more randomness
angle1 += random(-PI / 8, PI / 8);
angle2 += random(-PI / 8, PI / 8);
angle3 += random(-PI / 8, PI / 8);
angle4 += random(-PI / 8, PI / 8);
angle5 += random(-PI / 8, PI / 8);
// Create vectors from the angles
let v1 = p5.Vector.fromAngle(angle1);
let v2 = p5.Vector.fromAngle(angle2);
let v3 = p5.Vector.fromAngle(angle3);
let v4 = p5.Vector.fromAngle(angle4);
let v5 = p5.Vector.fromAngle(angle5);
// Scale the vectors
v1.mult(15000);
v2.mult(31000);
v3.mult(21000);
v4.mult(16000);
v5.mult(14000);
// Combine the vectors
return p5.Vector.add(p5.Vector.add(v1, v2), p5.Vector.add(v3, p5.Vector.add(v4, v5)));
}