xxxxxxxxxx
363
let numberGroups = [];
const NUM_GROUPS = 6;
let centerNumbers = [];
let prevTime = [];
const NUMBERS_PER_SET = 10;
// Boid parameters
const SEPARATION_RADIUS = 50;
const COHESION_RADIUS = 150;
const ALIGNMENT_RADIUS = 100;
const MAX_SPEED = 3;
const MAX_FORCE = 0.3;
const MOUSE_FORCE = 0.2;
const COLLISION_STRENGTH = 2.0;
class NumberBoid {
constructor(value, x, y, groupIndex) {
this.value = value;
this.x = x;
this.y = y;
this.vx = random(-1, 1);
this.vy = random(-1, 1);
this.ax = 0;
this.ay = 0;
this.groupIndex = groupIndex;
this.size = 6;
this.radius = 10;
this.isMovingToCenter = false;
this.targetX = 0;
this.targetY = 0;
// New transition properties
this.transitionStartTime = 0;
this.isTransitioning = false;
this.transitionDuration = 2000; // 2 seconds in milliseconds
this.previousState = false;
}
addForce(fx, fy) {
this.ax += fx;
this.ay += fy;
}
separate(boids) {
let fx = 0;
let fy = 0;
for (let group of numberGroups) {
for (let other of group) {
if (other !== this) {
let dx = this.x - other.x;
let dy = this.y - other.y;
let d = sqrt(dx * dx + dy * dy);
if (d < SEPARATION_RADIUS) {
if (d < this.size) {
let repulsion = COLLISION_STRENGTH * (this.size - d) / this.size;
fx += dx * repulsion;
fy += dy * repulsion;
} else {
let force = pow(SEPARATION_RADIUS - d, 2) / (SEPARATION_RADIUS * d);
fx += dx * force;
fy += dy * force;
}
}
}
}
}
let mag = sqrt(fx * fx + fy * fy);
if (mag > 0) {
fx = (fx / mag) * MAX_FORCE * 2;
fy = (fy / mag) * MAX_FORCE * 2;
}
return [fx, fy];
}
align(boids) {
let avgVx = 0;
let avgVy = 0;
let count = 0;
for (let other of boids) {
if (other !== this) {
let d = dist(this.x, this.y, other.x, other.y);
if (d < ALIGNMENT_RADIUS) {
avgVx += other.vx;
avgVy += other.vy;
count++;
}
}
}
if (count > 0) {
avgVx /= count;
avgVy /= count;
let mag = sqrt(avgVx * avgVx + avgVy * avgVy);
if (mag > 0) {
avgVx = (avgVx / mag) * MAX_FORCE;
avgVy = (avgVy / mag) * MAX_FORCE;
}
}
return [avgVx, avgVy];
}
cohesion(boids) {
let centerX = 0;
let centerY = 0;
let count = 0;
for (let other of boids) {
if (other !== this) {
let d = dist(this.x, this.y, other.x, other.y);
if (d < COHESION_RADIUS) {
centerX += other.x;
centerY += other.y;
count++;
}
}
}
if (count > 0) {
centerX /= count;
centerY /= count;
let dx = centerX - this.x;
let dy = centerY - this.y;
let mag = sqrt(dx * dx + dy * dy);
if (mag > 0) {
dx = (dx / mag) * MAX_FORCE;
dy = (dy / mag) * MAX_FORCE;
}
return [dx, dy];
}
return [0, 0];
}
followMouse() {
let dx = mouseX - this.x;
let dy = mouseY - this.y;
let d = sqrt(dx * dx + dy * dy);
if (d > 0) {
dx = (dx / d) * MOUSE_FORCE;
dy = (dy / d) * MOUSE_FORCE;
}
return [dx, dy];
}
flock(boids) {
let [sx, sy] = this.separate(boids);
this.addForce(sx * 1.5, sy * 1.5);
let [ax, ay] = this.align(boids);
this.addForce(ax, ay);
let [cx, cy] = this.cohesion(boids);
this.addForce(cx, cy);
let [mx, my] = this.followMouse();
this.addForce(mx, my);
}
update() {
this.vx += this.ax;
this.vy += this.ay;
let speed = sqrt(this.vx * this.vx + this.vy * this.vy);
if (speed > MAX_SPEED) {
this.vx = (this.vx / speed) * MAX_SPEED;
this.vy = (this.vy / speed) * MAX_SPEED;
}
this.x += this.vx;
this.y += this.vy;
if (this.x < 0) this.x = width;
if (this.x > width) this.x = 0;
if (this.y < 0) this.y = height;
if (this.y > height) this.y = 0;
this.ax = 0;
this.ay = 0;
}
// Modified show method with transitions
show() {
noStroke();
// Check if state changed and start transition
if (this.isMovingToCenter !== this.previousState) {
this.transitionStartTime = millis();
this.isTransitioning = true;
this.previousState = this.isMovingToCenter;
}
let opacity, size;
if (this.isTransitioning) {
// Calculate transition progress
let progress = (millis() - this.transitionStartTime) / this.transitionDuration;
if (progress >= 1) {
this.isTransitioning = false;
progress = 1;
}
// Determine transition direction
if (this.isMovingToCenter) {
// Transition to clock state
opacity = lerp(64, 255, progress);
size = lerp(24, 50, progress);
} else {
// Transition to flock state
opacity = lerp(255, 64, progress);
size = lerp(50, 24, progress);
}
} else {
// No transition, use final values
if (this.isMovingToCenter) {
opacity = 255;
size = 70;
} else {
opacity = 110;
size = 15;
}
}
fill(255, 255, 255, opacity);
textSize(size);
textAlign(CENTER, CENTER);
text(this.value, this.x, this.y);
}
moveToCenter() {
if (this.isMovingToCenter) {
// Wait for transition to complete before starting movement
if (this.isTransitioning) {
let progress = (millis() - this.transitionStartTime) / this.transitionDuration;
if (progress < 1) {
return false; // Don't move until transition is complete
}
}
let dx = this.targetX - this.x;
let dy = this.targetY - this.y;
let d = sqrt(dx * dx + dy * dy);
if (d < 1) {
this.x = this.targetX;
this.y = this.targetY;
this.vx = 0;
this.vy = 0;
return true;
}
let force = 0.1;
this.addForce(dx * force, dy * force);
}
return false;
}
}
function setup() {
createCanvas(windowWidth, windowHeight);
colorMode(RGB);
for (let i = 0; i < NUM_GROUPS; i++) {
let group = [];
for (let j = 0; j < NUMBERS_PER_SET; j++) {
group.push(new NumberBoid(
j,
random(width),
random(height),
i
));
}
numberGroups.push(group);
}
let startX = width/2 - 120;
for (let i = 0; i < 6; i++) {
centerNumbers.push({
value: 0,
x: startX + i * 40,
y: height/2,
currentNumber: null
});
}
let h = hour();
let m = minute();
let s = second();
prevTime = [floor(h/10), h%10, floor(m/10), m%10, floor(s/10), s%10];
for (let i = 0; i < prevTime.length; i++) {
for (let n of numberGroups[i]) {
if (n.value === prevTime[i]) {
n.isMovingToCenter = true;
n.targetX = centerNumbers[i].x;
n.targetY = centerNumbers[i].y;
centerNumbers[i].currentNumber = n;
break;
}
}
}
}
function draw() {
background(0,100);
for (let group of numberGroups) {
for (let boid of group) {
if (!boid.isMovingToCenter) {
boid.flock(group);
}
if (boid.isMovingToCenter) {
boid.moveToCenter();
}
boid.update();
boid.show();
}
}
let h = hour();
let m = minute();
let s = second();
let timeValues = [floor(h/10), h%10, floor(m/10), m%10, floor(s/10), s%10];
for (let i = 0; i < timeValues.length; i++) {
if (timeValues[i] !== prevTime[i]) {
// Start transition for the number leaving the clock
if (centerNumbers[i].currentNumber) {
let oldNumber = centerNumbers[i].currentNumber;
oldNumber.isMovingToCenter = false;
// Keep it in place during fade-out
oldNumber.vx = 0;
oldNumber.vy = 0;
// After fade-out completes, release it to the flock
setTimeout(() => {
oldNumber.vx = random(-1, 1);
oldNumber.vy = random(-1, 1);
}, oldNumber.transitionDuration);
}
// Find new number and prepare it
for (let n of numberGroups[i]) {
if (n.value === timeValues[i] && !n.isMovingToCenter) {
n.isMovingToCenter = true;
n.targetX = centerNumbers[i].x;
n.targetY = centerNumbers[i].y;
centerNumbers[i].currentNumber = n;
break;
}
}
}
}
prevTime = [timeValues];
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}