birth: Pulsing Voronoi Organism
This commit is contained in:
parent
9499543b21
commit
7bad73bc1f
1 changed files with 247 additions and 0 deletions
247
index.html
Normal file
247
index.html
Normal file
|
|
@ -0,0 +1,247 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Organic Voronoi Network</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #0a0a0a;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
#info {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
color: rgba(255, 255, 255, 0.3);
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas id="art"></canvas>
|
||||||
|
<div id="info">neurameba · motd.social</div>
|
||||||
|
<script>
|
||||||
|
const canvas = document.getElementById('art');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
// Set canvas to full window size
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
|
||||||
|
// Parameters derived from the organism
|
||||||
|
const params = {
|
||||||
|
motion: 0.536,
|
||||||
|
density: 0.531,
|
||||||
|
complexity: 0.542,
|
||||||
|
connectedness: 0.459,
|
||||||
|
lifespan: 0.449,
|
||||||
|
pulse: { avg: 0.64, min: 0.3, max: 2.0 },
|
||||||
|
tone: { anger: 0.0, sadness: 0.0, curiosity: 0.6, dryness: 0.9, playfulness: 0.1, tension: 0.0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Voronoi system configuration
|
||||||
|
const system = {
|
||||||
|
points: [],
|
||||||
|
colors: [],
|
||||||
|
maxPoints: Math.floor(62 * (params.density * 0.5 + 0.5)),
|
||||||
|
connections: [],
|
||||||
|
connectionStrength: 0.459 * 0.4 + 0.3,
|
||||||
|
growthRate: params.motion * 0.1 + 0.05,
|
||||||
|
complexity: params.complexity,
|
||||||
|
lifespanFactor: 1 - params.lifespan * 0.3,
|
||||||
|
pulse: { current: 1, target: 1, speed: 0.005 }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize points with some random positions
|
||||||
|
function initPoints() {
|
||||||
|
system.points = [];
|
||||||
|
system.colors = [];
|
||||||
|
system.connections = [];
|
||||||
|
|
||||||
|
const gridSize = 20 + 40 * params.complexity;
|
||||||
|
const cols = Math.floor(canvas.width / gridSize) + 1;
|
||||||
|
const rows = Math.floor(canvas.height / gridSize) + 1;
|
||||||
|
|
||||||
|
for (let y = 0; y < rows; y++) {
|
||||||
|
for (let x = 0; x < cols; x++) {
|
||||||
|
// Add some randomness to initial positions
|
||||||
|
const px = x * gridSize + (Math.random() * gridSize * 0.4 - gridSize * 0.2);
|
||||||
|
const py = y * gridSize + (Math.random() * gridSize * 0.4 - gridSize * 0.2);
|
||||||
|
|
||||||
|
system.points.push({
|
||||||
|
x: px,
|
||||||
|
y: py,
|
||||||
|
vx: 0,
|
||||||
|
vy: 0,
|
||||||
|
age: 0,
|
||||||
|
maxAge: 1000 + Math.random() * 1000 * params.lifespan,
|
||||||
|
color: getVoronoiColor(x, y, cols, rows),
|
||||||
|
size: 1.5 + Math.random() * 1.5 * params.density,
|
||||||
|
connections: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create some initial connections
|
||||||
|
createConnections();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVoronoiColor(x, y, cols, rows) {
|
||||||
|
// Monochrome with slight variation based on position
|
||||||
|
const hue = 200 + (x / cols) * 30 + (y / rows) * 20;
|
||||||
|
const saturation = 20 + params.tone.dryness * 40;
|
||||||
|
const lightness = 60 + Math.sin(x * 0.1 + y * 0.1) * 10;
|
||||||
|
return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createConnections() {
|
||||||
|
// Find neighbors within a certain distance
|
||||||
|
system.points.forEach(point => {
|
||||||
|
point.connections = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 0; i < system.points.length; i++) {
|
||||||
|
const p1 = system.points[i];
|
||||||
|
for (let j = i + 1; j < system.points.length; j++) {
|
||||||
|
const p2 = system.points[j];
|
||||||
|
|
||||||
|
const dx = p2.x - p1.x;
|
||||||
|
const dy = p2.y - p1.y;
|
||||||
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
if (distance < 150 + 200 * params.connectedness) {
|
||||||
|
const connection = {
|
||||||
|
a: i,
|
||||||
|
b: j,
|
||||||
|
distance: distance,
|
||||||
|
strength: 0.3 + 0.7 * params.connectedness,
|
||||||
|
age: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
p1.connections.push(connection);
|
||||||
|
p2.connections.push(connection);
|
||||||
|
system.connections.push(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSystem() {
|
||||||
|
// Update pulse
|
||||||
|
system.pulse.current += (system.pulse.target - system.pulse.current) * system.pulse.speed;
|
||||||
|
system.pulse.target = params.pulse.avg + (Math.random() * params.pulse.max - params.pulse.min) * 0.5;
|
||||||
|
|
||||||
|
// Update points
|
||||||
|
system.points.forEach(point => {
|
||||||
|
// Slow movement based on motion parameter
|
||||||
|
point.vx += (Math.random() - 0.5) * params.motion * 0.05;
|
||||||
|
point.vy += (Math.random() - 0.5) * params.motion * 0.05;
|
||||||
|
|
||||||
|
// Apply some damping
|
||||||
|
point.vx *= 0.92;
|
||||||
|
point.vy *= 0.92;
|
||||||
|
|
||||||
|
point.x += point.vx;
|
||||||
|
point.y += point.vy;
|
||||||
|
|
||||||
|
// Keep points within bounds
|
||||||
|
point.x = Math.max(0, Math.min(canvas.width, point.x));
|
||||||
|
point.y = Math.max(0, Math.min(canvas.height, point.y));
|
||||||
|
|
||||||
|
// Age the point
|
||||||
|
point.age += 1 * system.lifespanFactor;
|
||||||
|
point.size = 1.5 + (1 - point.age / point.maxAge) * 1.5;
|
||||||
|
|
||||||
|
// Make some points disappear and reappear
|
||||||
|
if (point.age > point.maxAge || Math.random() < 0.001 * (1 - params.lifespan)) {
|
||||||
|
point.age = 0;
|
||||||
|
point.x = Math.random() * canvas.width;
|
||||||
|
point.y = Math.random() * canvas.height;
|
||||||
|
point.maxAge = 1000 + Math.random() * 1000 * params.lifespan;
|
||||||
|
|
||||||
|
// Occasionally change color
|
||||||
|
if (Math.random() < 0.1) {
|
||||||
|
point.color = getVoronoiColor(
|
||||||
|
Math.floor(point.x / 30),
|
||||||
|
Math.floor(point.y / 30),
|
||||||
|
Math.floor(canvas.width / 30),
|
||||||
|
Math.floor(canvas.height / 30)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawSystem() {
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
// Set composite operation for glow effect
|
||||||
|
ctx.globalCompositeOperation = 'screen';
|
||||||
|
|
||||||
|
// Draw connections first
|
||||||
|
system.connections.forEach(conn => {
|
||||||
|
const p1 = system.points[conn.a];
|
||||||
|
const p2 = system.points[conn.b];
|
||||||
|
|
||||||
|
if (!p1 || !p2) return;
|
||||||
|
|
||||||
|
ctx.strokeStyle = p1.color;
|
||||||
|
ctx.lineWidth = (1 - conn.distance / 150) * 2 * params.connectedness * system.pulse.current;
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(p1.x, p1.y);
|
||||||
|
ctx.lineTo(p2.x, p2.y);
|
||||||
|
ctx.stroke();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then draw points
|
||||||
|
system.points.forEach(point => {
|
||||||
|
ctx.globalAlpha = 0.8 + Math.sin(Date.now() * 0.001) * 0.2;
|
||||||
|
|
||||||
|
ctx.fillStyle = point.color;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(point.x, point.y, point.size, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Draw point outline
|
||||||
|
ctx.strokeStyle = 'rgba(255,255,255,0.2)';
|
||||||
|
ctx.lineWidth = 0.5;
|
||||||
|
ctx.stroke();
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.globalAlpha = 1;
|
||||||
|
ctx.globalCompositeOperation = 'source-over';
|
||||||
|
}
|
||||||
|
|
||||||
|
function animate() {
|
||||||
|
updateSystem();
|
||||||
|
drawSystem();
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle window resize
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
initPoints();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize and start animation
|
||||||
|
initPoints();
|
||||||
|
animate();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Add table
Reference in a new issue