birth: Voronoi Pulse Fields
This commit is contained in:
parent
efb6ddbde2
commit
231c046013
1 changed files with 212 additions and 0 deletions
212
index.html
Normal file
212
index.html
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Voronoi Tremors</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
background: #0a0a0a;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
canvas {
|
||||
display: block;
|
||||
}
|
||||
#info {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
color: #888;
|
||||
font-size: 10px;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="canvas"></canvas>
|
||||
<div id="info">neurameba · motd.social</div>
|
||||
<script>
|
||||
const canvas = document.getElementById('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
function resize() {
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
}
|
||||
window.addEventListener('resize', resize);
|
||||
resize();
|
||||
|
||||
// Parameters from prompt
|
||||
const params = {
|
||||
motion: 0.5,
|
||||
density: 0.5,
|
||||
complexity: 0.5,
|
||||
connectedness: 0.5,
|
||||
lifespan: 0.5,
|
||||
pulse: { avg: 1.04, min: 0.75, max: 1.2 },
|
||||
tone: { anger: 0, sadness: 0, curiosity: 0.7, dryness: 0.9, playfulness: 0.1, tension: 0 }
|
||||
};
|
||||
|
||||
// Color palette based on tone
|
||||
const hueBase = params.tone.curiosity * 180;
|
||||
const saturation = params.tone.dryness < 0.5 ? 0.7 : 0.3;
|
||||
const brightness = 0.8 - params.tone.dryness * 0.5;
|
||||
const color = `hsl(${hueBase}, ${saturation * 100}%, ${brightness * 100}%)`;
|
||||
|
||||
// Voronoi parameters
|
||||
const cellCount = Math.floor(50 + params.density * 200);
|
||||
const points = [];
|
||||
const cells = [];
|
||||
const maxAge = 10000;
|
||||
const minDistance = 50 - params.density * 30;
|
||||
|
||||
// Initialize points
|
||||
for (let i = 0; i < cellCount; i++) {
|
||||
points.push({
|
||||
x: Math.random() * canvas.width,
|
||||
y: Math.random() * canvas.height,
|
||||
vx: 0,
|
||||
vy: 0,
|
||||
age: Math.random() * maxAge,
|
||||
lifespan: maxAge * (0.3 + Math.random() * 0.7) * params.lifespan
|
||||
});
|
||||
}
|
||||
|
||||
// Create voronoi diagram
|
||||
function createVoronoi() {
|
||||
cells.length = 0;
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
const p = points[i];
|
||||
const neighbors = [];
|
||||
const radius = minDistance;
|
||||
|
||||
// Find neighbors within radius
|
||||
for (let j = 0; j < points.length; j++) {
|
||||
if (i !== j) {
|
||||
const p2 = points[j];
|
||||
const dx = p2.x - p.x;
|
||||
const dy = p2.y - p.y;
|
||||
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (dist < radius) {
|
||||
neighbors.push(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cells.push({
|
||||
pointIndex: i,
|
||||
neighbors: neighbors,
|
||||
distance: radius
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Update points
|
||||
function updatePoints() {
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
const p = points[i];
|
||||
|
||||
// Random movement
|
||||
p.vx += (Math.random() - 0.5) * params.motion * 0.5;
|
||||
p.vy += (Math.random() - 0.5) * params.motion * 0.5;
|
||||
|
||||
// Friction
|
||||
p.vx *= 0.95;
|
||||
p.vy *= 0.95;
|
||||
|
||||
// Update position
|
||||
p.x += p.vx;
|
||||
p.y += p.vy;
|
||||
|
||||
// Boundary check
|
||||
if (p.x < 0 || p.x > canvas.width) p.vx *= -1;
|
||||
if (p.y < 0 || p.y > canvas.height) p.vy *= -1;
|
||||
|
||||
// Update age
|
||||
p.age += 1;
|
||||
|
||||
// Occasionally move to new position
|
||||
if (Math.random() < 0.005) {
|
||||
p.x = Math.random() * canvas.width;
|
||||
p.y = Math.random() * canvas.height;
|
||||
p.vx = 0;
|
||||
p.vy = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw voronoi cells
|
||||
function drawVoronoi() {
|
||||
ctx.strokeStyle = color;
|
||||
ctx.lineWidth = 1 + params.complexity * 2;
|
||||
ctx.fillStyle = `rgba(0, 0, 0, ${0.7 - params.tone.dryness * 0.6})`;
|
||||
|
||||
for (let i = 0; i < cells.length; i++) {
|
||||
const cell = cells[i];
|
||||
const p = points[cell.pointIndex];
|
||||
|
||||
// Only draw if point is alive
|
||||
if (p.age < p.lifespan || p.age - p.lifespan < 100) {
|
||||
// Draw polygon
|
||||
ctx.beginPath();
|
||||
const neighbors = cell.neighbors;
|
||||
|
||||
if (neighbors.length > 0) {
|
||||
// Move to first neighbor midpoint
|
||||
const first = points[neighbors[0]];
|
||||
ctx.moveTo(
|
||||
(p.x + first.x) / 2,
|
||||
(p.y + first.y) / 2
|
||||
);
|
||||
|
||||
// Draw to each neighbor
|
||||
for (let j = 0; j < neighbors.length; j++) {
|
||||
const n = points[neighbors[j]];
|
||||
ctx.lineTo(
|
||||
(p.x + n.x) / 2,
|
||||
(p.y + n.y) / 2
|
||||
);
|
||||
}
|
||||
|
||||
// Close path
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw points with pulse effect
|
||||
const pulseScale = params.pulse.avg + Math.sin(Date.now() / 500) * 0.1;
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
const p = points[i];
|
||||
if (p.age < p.lifespan || p.age - p.lifespan < 100) {
|
||||
const size = 2 + pulseScale * (0.5 + params.complexity * 0.5);
|
||||
ctx.fillStyle = color;
|
||||
ctx.beginPath();
|
||||
ctx.arc(p.x, p.y, size, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Animation loop
|
||||
function animate() {
|
||||
ctx.fillStyle = `rgba(0, 0, 0, ${0.1 * params.tone.dryness})`;
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
updatePoints();
|
||||
createVoronoi();
|
||||
drawVoronoi();
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
animate();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Reference in a new issue