fractured-symmetry-in-motio.../index.html

240 lines
No EOL
8.3 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Voronoi Organism</title>
<style>
body { margin: 0; overflow: hidden; background: #0a0a0a; }
canvas { display: block; }
#attribution { position: absolute; bottom: 10px; right: 10px; color: #444; font-family: monospace; font-size: 10px; }
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<div id="attribution">neurameba · motd.social</div>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// Set canvas to full window size
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// Parameters
const params = {
motion: 0.580,
density: 0.358,
complexity: 0.726,
connectedness: 0.530,
lifespan: 0.340,
pulse: { avg: 0.35, min: 0.30, max: 1.20 },
tone: { anger: 0.00, sadness: 0.00, curiosity: 0.20, dryness: 0.90, playfulness: 0.00, tension: 0.00 }
};
// Generate Voronoi diagram with dynamic points
let points = [];
const pointCount = Math.max(50, Math.floor(params.density * 200 + 50));
const maxSpeed = params.motion * 2 + 0.5;
// Initialize points
for (let i = 0; i < pointCount; i++) {
points.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * maxSpeed,
vy: (Math.random() - 0.5) * maxSpeed,
color: `hsl(0, 0%, ${Math.random() * 20 + 20}%)`,
size: Math.random() * 2 + 1,
energy: 38.8
});
}
// Voronoi diagram data
let edges = [];
let cells = [];
// Distance function
function distance(p1, p2) {
return Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2);
}
// Bounding box intersection
function lineBoxIntersection(x1, y1, x2, y2, xmin, ymin, xmax, ymax) {
const d1 = (x1 < xmin && x2 < xmin) || (x1 > xmax && x2 > xmax) ||
(y1 < ymin && y2 < ymin) || (y1 > ymax && y2 > ymax);
if (d1) return false;
const coef1 = (y2 - y1) / (x2 - x1);
const coef2 = y1 - coef1 * x1;
let x = xmin;
let y = coef1 * x + coef2;
if (y >= ymin && y <= ymax) return true;
x = xmax;
y = coef1 * x + coef2;
if (y >= ymin && y <= ymax) return true;
y = ymin;
x = (y - coef2) / coef1;
if (x >= xmin && x <= xmax) return true;
y = ymax;
x = (y - coef2) / coef1;
if (x >= xmin && x <= xmax) return true;
return false;
}
// Calculate Voronoi diagram (simplified)
function calculateVoronoi() {
edges = [];
cells = points.map((_, i) => ({ site: i, edges: [], neighbors: [] }));
// For each point, find its nearest neighbors and potential edges
for (let i = 0; i < points.length; i++) {
const p1 = points[i];
let closest = null;
let minDist = Infinity;
for (let j = i + 1; j < points.length; j++) {
const p2 = points[j];
const dist = distance(p1, p2);
if (dist < minDist) {
minDist = dist;
closest = j;
}
}
if (closest !== null) {
// Create bidirectional edge
const edge = {
p1: i,
p2: closest,
length: minDist,
color: p1.color,
width: 1 + (p1.size + points[closest].size) / 4
};
cells[i].edges.push(edge);
cells[closest].edges.push(edge);
cells[i].neighbors.push(closest);
cells[closest].neighbors.push(i);
edges.push(edge);
}
}
}
// Update points
function updatePoints() {
const pulseFactor = params.pulse.avg + (Math.random() - 0.5) * params.pulse.max * 0.1;
points.forEach(p => {
// Move with slight bias toward center
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const dx = centerX - p.x;
const dy = centerY - p.y;
const dist = Math.sqrt(dx * dx + dy * dy);
p.vx += dx / (dist * 100 + 100) * params.motion * 0.5;
p.vy += dy / (dist * 100 + 100) * params.motion * 0.5;
// Apply speed limit
const speed = Math.sqrt(p.vx * p.vx + p.vy * p.vy);
if (speed > maxSpeed) {
p.vx = (p.vx / speed) * maxSpeed;
p.vy = (p.vy / speed) * maxSpeed;
}
// Update position
p.x += p.vx * pulseFactor;
p.y += p.vy * pulseFactor;
// Boundary check
if (p.x < 0 || p.x > canvas.width) p.vx *= -0.5;
if (p.y < 0 || p.y > canvas.height) p.vy *= -0.5;
// Random small variations
p.vx += (Math.random() - 0.5) * 0.1 * params.motion;
p.vy += (Math.random() - 0.5) * 0.1 * params.motion;
// Fade color based on lifespan
if (params.lifespan < 0.5) {
const alpha = params.lifespan * 2;
const hsl = p.color.match(/\d+/g);
hsl[2] = Math.max(5, parseInt(hsl[2]) * alpha);
p.color = `hsl(${hsl[0]}, ${hsl[1]}%, ${hsl[2]}%)`;
}
});
}
// Draw Voronoi edges
function drawVoronoi() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw edges with glow
edges.forEach(edge => {
ctx.strokeStyle = edge.color;
ctx.lineWidth = edge.width * (0.5 + params.complexity * 0.5);
ctx.beginPath();
const p1 = points[edge.p1];
const p2 = points[edge.p2];
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.stroke();
// Add glow effect
ctx.shadowBlur = 10 * params.complexity;
ctx.shadowColor = edge.color;
ctx.stroke();
ctx.shadowBlur = 0;
});
// Draw points
points.forEach(p => {
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size * (0.5 + params.density * 0.5), 0, Math.PI * 2);
ctx.fill();
});
}
// Animation loop
function animate() {
updatePoints();
calculateVoronoi();
drawVoronoi();
// Randomly add/remove points based on density
if (Math.random() < 0.02) {
if (points.length < pointCount * 1.5 && Math.random() < params.density) {
points.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * maxSpeed,
vy: (Math.random() - 0.5) * maxSpeed,
color: `hsl(0, 0%, ${Math.random() * 20 + 20}%)`,
size: Math.random() * 2 + 1,
energy: 38.8
});
} else if (points.length > pointCount * 0.5 && Math.random() > params.density) {
points.splice(Math.floor(Math.random() * points.length), 1);
}
}
requestAnimationFrame(animate);
}
animate();
</script>
</body>
</html>