birth: Veinous Voronoi Tides
This commit is contained in:
parent
b8f5b6d265
commit
ef3d43b57c
1 changed files with 270 additions and 0 deletions
270
index.html
Normal file
270
index.html
Normal file
|
|
@ -0,0 +1,270 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Vibrant Voronoi Veins</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
background-color: #0a0a1a;
|
||||||
|
color: #e0e0e0;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.attribution {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
left: 10px;
|
||||||
|
font-size: 10px;
|
||||||
|
color: #666;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 30px;
|
||||||
|
left: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #aaa;
|
||||||
|
z-index: 10;
|
||||||
|
writing-mode: vertical-rl;
|
||||||
|
text-orientation: mixed;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas id="canvas"></canvas>
|
||||||
|
<div class="attribution">neurameba · motd.social</div>
|
||||||
|
<div class="title">Veinous Voronoi</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 based on generative art spec
|
||||||
|
const params = {
|
||||||
|
motion: 0.574,
|
||||||
|
density: 0.430,
|
||||||
|
complexity: 0.540,
|
||||||
|
connectedness: 0.486,
|
||||||
|
lifespan: 0.469,
|
||||||
|
pulse: {
|
||||||
|
avg: 0.51,
|
||||||
|
min: 0.30,
|
||||||
|
max: 2.00
|
||||||
|
},
|
||||||
|
tone: {
|
||||||
|
anger: 0.00,
|
||||||
|
sadness: 0.00,
|
||||||
|
curiosity: 0.30,
|
||||||
|
dryness: 0.90,
|
||||||
|
playfulness: 0.10,
|
||||||
|
tension: 0.00
|
||||||
|
},
|
||||||
|
topology: {
|
||||||
|
survivingNodes: 142,
|
||||||
|
branchCount: 124,
|
||||||
|
loops: 771,
|
||||||
|
maxDepth: 42,
|
||||||
|
thicknessRatio: 1.75,
|
||||||
|
fractalDimension: 1.616,
|
||||||
|
finalEnergy: 1164.6
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Voronoi diagram with dynamic points
|
||||||
|
let points = [];
|
||||||
|
let velocities = [];
|
||||||
|
let time = 0;
|
||||||
|
let hueOffset = 0;
|
||||||
|
|
||||||
|
// Initialize points based on density
|
||||||
|
function initPoints() {
|
||||||
|
const count = Math.floor(100 + params.topology.survivingNodes * 5);
|
||||||
|
points = [];
|
||||||
|
velocities = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
points.push({
|
||||||
|
x: Math.random() * canvas.width,
|
||||||
|
y: Math.random() * canvas.height
|
||||||
|
});
|
||||||
|
velocities.push({
|
||||||
|
x: (Math.random() - 0.5) * params.motion * 5,
|
||||||
|
y: (Math.random() - 0.5) * params.motion * 5
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update points positions with boundary checks
|
||||||
|
function updatePoints() {
|
||||||
|
for (let i = 0; i < points.length; i++) {
|
||||||
|
points[i].x += velocities[i].x;
|
||||||
|
points[i].y += velocities[i].y;
|
||||||
|
|
||||||
|
// Boundary collision
|
||||||
|
if (points[i].x < 0 || points[i].x > canvas.width) {
|
||||||
|
velocities[i].x *= -0.8;
|
||||||
|
}
|
||||||
|
if (points[i].y < 0 || points[i].y > canvas.height) {
|
||||||
|
velocities[i].y *= -0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw voronoi diagram
|
||||||
|
function drawVoronoi() {
|
||||||
|
// Clear with slightly fading background
|
||||||
|
ctx.fillStyle = `rgba(10, 10, 26, ${0.05 + params.pulse.avg * 0.05})`;
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
// Create voronoi diagram
|
||||||
|
const diagram = new Voronoi();
|
||||||
|
const bbox = {xl: 0, xr: canvas.width, yt: 0, yb: canvas.height};
|
||||||
|
const sites = points.map(p => ({x: p.x, y: p.y}));
|
||||||
|
const voronoi = diagram.compute(sites, bbox);
|
||||||
|
|
||||||
|
// Draw cells
|
||||||
|
if (voronoi) {
|
||||||
|
voronoi.cells.forEach((cell, i) => {
|
||||||
|
if (cell.halfedges.length === 0) return;
|
||||||
|
|
||||||
|
// Close the cell
|
||||||
|
const first = cell.halfedges[0].getStartpoint();
|
||||||
|
const last = cell.halfedges[cell.halfedges.length - 1].getEndpoint();
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(first.x, first.y);
|
||||||
|
|
||||||
|
for (let j = 0; j < cell.halfedges.length; j++) {
|
||||||
|
const edge = cell.halfedges[j];
|
||||||
|
const end = edge.getEndpoint();
|
||||||
|
ctx.lineTo(end.x, end.y);
|
||||||
|
}
|
||||||
|
ctx.closePath();
|
||||||
|
|
||||||
|
// Style based on tone parameters
|
||||||
|
const hue = (hueOffset + i * 0.5) % 360;
|
||||||
|
const saturation = 70 + params.tone.curiosity * 20;
|
||||||
|
const lightness = 60 + Math.sin(time * 0.001 + i * 0.1) * 10;
|
||||||
|
const alpha = 0.3 + params.pulse.avg * 0.2;
|
||||||
|
|
||||||
|
// Apply dryness (monochrome) or playfulness (color)
|
||||||
|
if (params.tone.dryness > 0.8) {
|
||||||
|
const gray = 100 + Math.sin(time * 0.001 + i * 0.1) * 20;
|
||||||
|
ctx.fillStyle = `rgba(${gray}, ${gray}, ${gray}, ${alpha})`;
|
||||||
|
} else {
|
||||||
|
ctx.fillStyle = `hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Only stroke if complexity is high
|
||||||
|
if (params.complexity > 0.5) {
|
||||||
|
ctx.strokeStyle = `hsla(${hue}, 80%, 50%, ${alpha * 0.5})`;
|
||||||
|
ctx.lineWidth = 1 + params.topology.thicknessRatio * 0.5;
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw points as subtle markers
|
||||||
|
if (params.complexity > 0.7) {
|
||||||
|
points.forEach(p => {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(p.x, p.y, 1, 0, Math.PI * 2);
|
||||||
|
ctx.fillStyle = `hsla(${hueOffset % 360}, 100%, 80%, 0.2)`;
|
||||||
|
ctx.fill();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
time += 1;
|
||||||
|
hueOffset += 0.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animation loop
|
||||||
|
function animate() {
|
||||||
|
updatePoints();
|
||||||
|
drawVoronoi();
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple Voronoi implementation for demonstration
|
||||||
|
function Voronoi() {}
|
||||||
|
|
||||||
|
Voronoi.prototype.compute = function(sites, bbox) {
|
||||||
|
if (!sites || sites.length === 0) return null;
|
||||||
|
|
||||||
|
// Create diagram structure
|
||||||
|
const diagram = {
|
||||||
|
cells: [],
|
||||||
|
edges: [],
|
||||||
|
vertices: []
|
||||||
|
};
|
||||||
|
|
||||||
|
// For this demo, we'll simulate a simple version
|
||||||
|
// In a real implementation, you'd use Fortune's algorithm
|
||||||
|
|
||||||
|
for (let i = 0; i < sites.length; i++) {
|
||||||
|
const cell = {
|
||||||
|
site: sites[i],
|
||||||
|
halfedges: []
|
||||||
|
};
|
||||||
|
diagram.cells.push(cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create edges between adjacent cells
|
||||||
|
for (let i = 0; i < sites.length; i++) {
|
||||||
|
for (let j = i + 1; j < sites.length; j++) {
|
||||||
|
const p1 = sites[i];
|
||||||
|
const p2 = sites[j];
|
||||||
|
|
||||||
|
// Create a dividing line (simplified)
|
||||||
|
const dx = p2.x - p1.x;
|
||||||
|
const dy = p2.y - p1.y;
|
||||||
|
const length = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
const nx = -dy / length;
|
||||||
|
const ny = dx / length;
|
||||||
|
|
||||||
|
// Add edges to both cells
|
||||||
|
diagram.cells[i].halfedges.push({
|
||||||
|
getStartpoint: () => ({x: p2.x, y: p2.y}),
|
||||||
|
getEndpoint: () => ({x: p1.x, y: p1.y})
|
||||||
|
});
|
||||||
|
|
||||||
|
diagram.cells[j].halfedges.push({
|
||||||
|
getStartpoint: () => ({x: p1.x, y: p1.y}),
|
||||||
|
getEndpoint: () => ({x: p2.x, y: p2.y})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return diagram;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start animation
|
||||||
|
initPoints();
|
||||||
|
animate();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Add table
Reference in a new issue