birth: Fractured Symmetry in Motion
This commit is contained in:
parent
01de174cdb
commit
cfef994a16
1 changed files with 240 additions and 0 deletions
240
index.html
Normal file
240
index.html
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
<!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>
|
||||
Loading…
Add table
Reference in a new issue