xxxxxxxxxx
165
let particles = [];
let numCircles = 5; // Number of cultural evolution rings
let maxRadius;
let centerStage = 0; // Start at the innermost ring (0 = innermost ring, 1 = outermost)
let expansionSpeed = 0.0020; // Controls outward movement speed
let resetting = false;
let fadeOutTimer = 100; // Time before restart
let stageLabels = ["Archaic", "Magic", "Mythic", "Rational", "Pluralistic"];
function setup() {
createCanvas(600, 600);
maxRadius = width / 2 - 20;
}
function draw() {
background(0, 50);
// Draw concentric circles
drawConcentricCircles();
if (!resetting) {
// Move the cultural center outward over time
centerStage += expansionSpeed;
if (centerStage >= 1) {
resetting = true; // Trigger fade-out when we reach the final ring
fadeOutTimer = 100; // Hold before reset
}
} else {
// Fade out particles before restarting
fadeOutTimer--;
if (fadeOutTimer <= 0) {
particles = []; // Clear existing particles
centerStage = 0; // Restart from the center
resetting = false;
}
}
// Increase the number of particles over time (population growth)
let spawnRate = map(centerStage, 0, 1, 2, 15); // More dots as time progresses
for (let i = 0; i < spawnRate; i++) {
if (!resetting) {
let angle = random(TWO_PI);
let radius = getClusteredRadius(); // Stronger clustering in dominant ring
let x = width / 2 + cos(angle) * radius;
let y = height / 2 + sin(angle) * radius;
let lifespan = random(60, 120);
particles.push(new Particle(x, y, lifespan));
}
}
// Update and display particles
for (let i = particles.length - 1; i >= 0; i--) {
particles[i].update();
particles[i].show();
if (particles[i].lifespan <= 0) {
particles.splice(i, 1);
}
}
// Draw labels LAST so they are **on top of everything**
drawLabels();
}
// Function to draw concentric circles
function drawConcentricCircles() {
noFill();
stroke(100, 100, 255, 100);
strokeWeight(1.5);
for (let i = 0; i < numCircles; i++) {
let radius = ((i + 1) / numCircles) * maxRadius;
ellipse(width / 2, height / 2, radius * 2, radius * 2);
}
}
// Function to draw labels with improved readability
function drawLabels() {
textAlign(CENTER, CENTER);
textSize(16); // Slightly larger for better visibility
textStyle(BOLD); // Bold text for stronger contrast
for (let i = 0; i < numCircles; i++) {
let radius = ((i + 1) / numCircles) * maxRadius;
// Position labels **further down inside** the corresponding ring
let labelY = height / 2 - radius + 20; // Increased offset for better separation
// **Thicker black outline (shadow) for readability**
fill(0);
stroke(0);
strokeWeight(6);
text(stageLabels[i], width / 2, labelY);
// **Blue text overlay**
fill(100, 100, 255);
noStroke();
text(stageLabels[i], width / 2, labelY);
}
}
// Custom function to **only cluster dots around the current cultural center**
function getClusteredRadius() {
let dominantRing = centerStage * maxRadius; // The main cultural ring at this moment
// Increase the "thickness" of the cultural center over time
let maxThickness = maxRadius * 0.15; // Prevent excessive spread
let centerThickness = constrain(map(pow(centerStage, 0.75), 0, 1, maxRadius * 0.05, maxThickness), maxRadius * 0.05, maxThickness);
let r = random();
// Probability of appearing at different distances
let probCurrent = 0.8; // 80% in dominant ring
let probPast = map(centerStage, 0, 1, 0.2, 0.1); // Past stages fade but don’t disappear
let probFutureNear = map(pow(centerStage, 1.5), 0, 1, 0.02, 0.15); // Small jump (next ring)
let probFutureMid = map(pow(centerStage, 2), 0, 1, 0.005, 0.07); // Medium jump
let probFutureFar = map(pow(centerStage, 3), 0, 1, 0, 0.03); // Large jump (outermost)
let probIntegral = map(pow(centerStage, 4), 0, 1, 0, 0.05); // Beyond the rings
let choice = random();
if (choice < probCurrent) {
return randomGaussian(dominantRing, centerThickness); // Cluster broadens gradually
} else if (choice < probCurrent + probPast) {
return random(dominantRing * 0.5, dominantRing); // Some still appear in previous rings
} else if (choice < probCurrent + probPast + probFutureNear) {
return random(dominantRing, dominantRing + maxRadius / numCircles); // Next closest ring
} else if (choice < probCurrent + probPast + probFutureNear + probFutureMid) {
return random(dominantRing + maxRadius / numCircles, dominantRing + (2 * maxRadius / numCircles)); // Mid-range jump
} else if (choice < probCurrent + probPast + probFutureNear + probFutureMid + probFutureFar) {
return random(dominantRing + (2 * maxRadius / numCircles), maxRadius); // Far jump (only when center is near it)
} else {
return random(maxRadius, maxRadius * 1.5); // **Integral pioneers expanding beyond the rings!**
}
}
// Particle class
class Particle {
constructor(x, y, lifespan) {
this.x = x;
this.y = y;
this.alpha = 255;
this.lifespan = lifespan;
}
update() {
if (resetting) {
this.alpha -= 5; // Fade out quickly during reset
}
this.lifespan--;
this.alpha = max(0, map(this.lifespan, 0, 120, 0, 255));
}
show() {
noStroke();
fill(255, 204, 0, this.alpha);
ellipse(this.x, this.y, 6, 6);
}
}