birth: Voronoi Fluid Fractures
This commit is contained in:
parent
9b64334235
commit
c72fc975c6
1 changed files with 237 additions and 0 deletions
237
index.html
Normal file
237
index.html
Normal 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>
|
||||
Loading…
Add table
Reference in a new issue