birth: Voronoi Fluid Fractures

This commit is contained in:
motd_admin 2026-04-17 13:47:22 +00:00
parent 9b64334235
commit c72fc975c6

237
index.html Normal file
View file

@ -0,0 +1,237 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Voronoi Nebula</title>
<style>
body {
margin: 0;
overflow: hidden;
background: #0a0a1a;
font-family: 'Courier New', monospace;
}
canvas {
display: block;
background: transparent;
}
#info {
position: absolute;
bottom: 10px;
left: 0;
right: 0;
color: #fff;
text-align: center;
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.606,
density: 0.563,
complexity: 0.528,
connectedness: 0.376,
lifespan: 0.463,
loops: 118,
maxDepth: 10,
thicknessRatio: 1.25,
fractalDim: 0.5,
finalEnergy: 64.8,
pulse: { avg: 0.36, min: 0.3, max: 1.35 },
tone: { curiosity: 0.8, dryness: 0.9 }
};
// Voronoi setup
const points = [];
const cells = [];
const edges = [];
const colors = [];
// Generate points
const pointCount = Math.max(10, Math.floor(100 * params.density));
for (let i = 0; i < pointCount; i++) {
points.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * 2 * params.motion,
vy: (Math.random() - 0.5) * 2 * params.motion,
lifetime: params.lifespan > 0.5 ? 1000 : 200 + Math.random() * 100
});
}
// Colors based on tone
const baseColor = params.tone.dryness > 0.7 ? [0.9, 0.9, 0.9] : [0.2, 0.8, 0.7];
const saturation = params.tone.dryness > 0.7 ? 0.1 : 0.8;
const lightness = 0.7 + Math.sin(Date.now() * 0.001) * 0.1;
// Animation state
let time = 0;
function animate() {
time += 1;
// Clear with subtle trail
ctx.fillStyle = `rgba(10, 10, 26, ${0.05 + 0.05 * params.motion})`;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Update points
points.forEach(p => {
p.x += p.vx;
p.y += p.vy;
if (p.lifetime-- < 0) {
// Regenerate point if faded
p.x = Math.random() * canvas.width;
p.y = Math.random() * canvas.height;
p.vx = (Math.random() - 0.5) * 2 * params.motion;
p.vy = (Math.random() - 0.5) * 2 * params.motion;
p.lifetime = params.lifespan > 0.5 ? 1000 : 200 + Math.random() * 100;
}
// Edge wrapping
if (p.x < 0) p.x = canvas.width;
if (p.x > canvas.width) p.x = 0;
if (p.y < 0) p.y = canvas.height;
if (p.y > canvas.height) p.y = 0;
});
// Calculate Voronoi diagram
const voronoi = new Voronoi();
const bbox = { xl: 0, xr: canvas.width, yt: 0, yb: canvas.height };
const diagram = voronoi.compute(points, bbox);
// Draw Voronoi cells
ctx.strokeStyle = `hsla(${200 + Math.sin(time*0.001)*30}, ${saturation*100}%, ${lightness*100}%, 0.3)`;
ctx.lineWidth = 0.5 + params.thicknessRatio * 0.5;
diagram.cells.forEach(cell => {
if (!cell.halfedges.length) return;
const first = cell.halfedges[0].getStartpoint();
ctx.beginPath();
ctx.moveTo(first.x, first.y);
for (let i = 0; i < cell.halfedges.length; i++) {
const edge = cell.halfedges[i];
const end = edge.getEndpoint();
ctx.lineTo(end.x, end.y);
}
ctx.closePath();
ctx.stroke();
// Fill with semi-transparent color
const hue = 200 + Math.floor(cell.site.y % 30);
const sat = saturation * 80;
const light = lightness * 100 + Math.sin(cell.site.x * 0.01 + time*0.001) * 10;
ctx.fillStyle = `hsla(${hue}, ${sat}%, ${light}%, 0.1)`;
ctx.fill();
});
requestAnimationFrame(animate);
}
// Simple Voronoi implementation (diagram only)
function Voronoi() {
this.compute = function(sites, bbox) {
const diagram = {
cells: [],
edges: [],
vertices: []
};
// Simple centroidal Voronoi with Lloyd relaxation
for (let i = 0; i < 3; i++) {
diagram.cells = [];
const diagramEdges = [];
// Assign each point to cells (simplified)
const cells = sites.map(site => ({
site,
halfedges: [],
close: (p1, p2) => {
const edge = { leftSite: site, rightSite: p2 };
site.halfedges.push({
edge,
getStartpoint: () => site,
getEndpoint: () => p2
});
diagramEdges.push(edge);
return edge;
}
}));
// Connect neighbors (simplified adjacency)
for (let i = 0; i < sites.length; i++) {
const neighbors = [];
for (let j = 0; j < sites.length; j++) {
if (i === j) continue;
const dx = sites[j].x - sites[i].x;
const dy = sites[j].y - sites[i].y;
const dist = dx*dx + dy*dy;
if (dist < 20000 * params.density) {
neighbors.push(sites[j]);
}
}
if (neighbors.length > 0) {
// Connect to nearest neighbors
neighbors.sort((a,b) => {
const dx1 = a.x - sites[i].x;
const dy1 = a.y - sites[i].y;
const dx2 = b.x - sites[i].x;
const dy2 = b.y - sites[i].y;
return (dx1*dx1 + dy1*dy1) - (dx2*dx2 + dy2*dy2);
});
for (let k = 0; k < Math.min(3, neighbors.length); k++) {
cells[i].close(neighbors[k]);
}
}
}
diagram.cells = cells;
this.cells = cells;
// Update sites positions slightly (Lloyd relaxation)
sites.forEach((site, i) => {
let cx = 0, cy = 0, count = 0;
cells[i].halfedges.forEach(h => {
const p = h.getEndpoint();
cx += p.x;
cy += p.y;
count++;
});
if (count > 0) {
site.x = cx / count;
site.y = cy / count;
}
});
}
return diagram;
};
}
animate();
})();
</script>
</body>
</html>