birth: Voronoi Bloom Fracture
This commit is contained in:
parent
61a403ad19
commit
968e6cce98
1 changed files with 280 additions and 0 deletions
280
index.html
Normal file
280
index.html
Normal file
|
|
@ -0,0 +1,280 @@
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Voronoi Bloom</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #0a0a0a;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
#attribution {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 10px;
|
||||||
|
right: 10px;
|
||||||
|
color: #444;
|
||||||
|
font-size: 10px;
|
||||||
|
text-shadow: 0 0 5px #222;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas id="canvas"></canvas>
|
||||||
|
<div id="attribution">neurameba · motd.social</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
|
||||||
|
const params = {
|
||||||
|
motion: 0.5,
|
||||||
|
density: 0.5,
|
||||||
|
complexity: 0.5,
|
||||||
|
connectedness: 0.5,
|
||||||
|
lifespan: 0.5,
|
||||||
|
pulse: {
|
||||||
|
avg: 1.25,
|
||||||
|
min: 1.00,
|
||||||
|
max: 1.55
|
||||||
|
},
|
||||||
|
tone: {
|
||||||
|
anger: 0.00,
|
||||||
|
sadness: 0.00,
|
||||||
|
curiosity: 0.10,
|
||||||
|
dryness: 0.90,
|
||||||
|
playfulness: 0.00,
|
||||||
|
tension: 0.00
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Voronoi cell structure
|
||||||
|
class VoronoiCell {
|
||||||
|
constructor(x, y, id) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.id = id;
|
||||||
|
this.neighbors = [];
|
||||||
|
this.radius = 2 + Math.random() * 4;
|
||||||
|
this.targetRadius = this.radius;
|
||||||
|
this.pulseTime = 0;
|
||||||
|
this.pulseSpeed = params.pulse.avg * (0.5 + Math.random());
|
||||||
|
this.pulseDirection = 1;
|
||||||
|
this.lifespan = params.lifespan * 1000 * (0.5 + Math.random());
|
||||||
|
this.age = 0;
|
||||||
|
this.color = Math.random() > 0.8 ? '#ddd' : '#888';
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
this.age += 16; // Approx frame time
|
||||||
|
|
||||||
|
// Pulsing effect
|
||||||
|
this.pulseTime += 0.02 * this.pulseSpeed * this.pulseDirection;
|
||||||
|
if (this.pulseTime > 1) {
|
||||||
|
this.pulseTime = 1;
|
||||||
|
this.pulseDirection = -1;
|
||||||
|
} else if (this.pulseTime < 0) {
|
||||||
|
this.pulseTime = 0;
|
||||||
|
this.pulseDirection = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Morphing target radius
|
||||||
|
this.targetRadius = 2 + 8 * (Math.sin(this.pulseTime * Math.PI) * 0.5 + 0.5) * (0.5 + params.lifespan);
|
||||||
|
|
||||||
|
// Lifespan decay
|
||||||
|
if (params.lifespan < 0.8) {
|
||||||
|
const decay = 1 - (this.age / this.lifespan);
|
||||||
|
this.targetRadius *= decay;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Smooth interpolation
|
||||||
|
this.radius += (this.targetRadius - this.radius) * 0.05;
|
||||||
|
|
||||||
|
// Fade out if lifespan is low
|
||||||
|
if (params.lifespan < 0.3) {
|
||||||
|
const alpha = Math.min(1, this.age / 500);
|
||||||
|
this.color = `rgba(255, 255, 255, ${0.3 * alpha})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(other) {
|
||||||
|
if (Math.random() < params.connectedness * 0.3) {
|
||||||
|
this.neighbors.push(other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
draw(ctx) {
|
||||||
|
// Draw cell
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
|
||||||
|
ctx.fillStyle = this.color;
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Draw connections
|
||||||
|
if (params.connectedness > 0.3) {
|
||||||
|
ctx.strokeStyle = `rgba(255, 255, 255, ${0.1 + params.connectedness * 0.3})`;
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.beginPath();
|
||||||
|
for (const neighbor of this.neighbors) {
|
||||||
|
ctx.moveTo(this.x, this.y);
|
||||||
|
ctx.lineTo(neighbor.x, neighbor.y);
|
||||||
|
}
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Voronoi diagram generator
|
||||||
|
class Voronoi {
|
||||||
|
constructor() {
|
||||||
|
this.cells = [];
|
||||||
|
this.edges = [];
|
||||||
|
this.diagram = null;
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
// Create initial points
|
||||||
|
const pointCount = Math.floor(100 + params.density * 400);
|
||||||
|
for (let i = 0; i < pointCount; i++) {
|
||||||
|
this.cells.push(new VoronoiCell(
|
||||||
|
Math.random() * canvas.width,
|
||||||
|
Math.random() * canvas.height,
|
||||||
|
i
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build initial diagram
|
||||||
|
this.buildDiagram();
|
||||||
|
}
|
||||||
|
|
||||||
|
buildDiagram() {
|
||||||
|
// Simple Voronoi approximation using distance-based regions
|
||||||
|
this.diagram = this.cells.map(cell => {
|
||||||
|
return {
|
||||||
|
cell: cell,
|
||||||
|
points: []
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// For each pixel, determine closest cell
|
||||||
|
const imageData = ctx.createImageData(canvas.width, canvas.height);
|
||||||
|
const data = imageData.data;
|
||||||
|
|
||||||
|
for (let y = 0; y < canvas.height; y++) {
|
||||||
|
for (let x = 0; x < canvas.width; x++) {
|
||||||
|
let minDist = Infinity;
|
||||||
|
let closest = null;
|
||||||
|
|
||||||
|
// Find closest cell
|
||||||
|
for (const cell of this.cells) {
|
||||||
|
const dx = x - cell.x;
|
||||||
|
const dy = y - cell.y;
|
||||||
|
const dist = dx * dx + dy * dy;
|
||||||
|
|
||||||
|
if (dist < minDist) {
|
||||||
|
minDist = dist;
|
||||||
|
closest = cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Color based on closest cell
|
||||||
|
const idx = (y * canvas.width + x) * 4;
|
||||||
|
const grey = Math.floor(80 + minDist * 0.01 * params.complexity);
|
||||||
|
data[idx] = grey;
|
||||||
|
data[idx + 1] = grey;
|
||||||
|
data[idx + 2] = grey;
|
||||||
|
data[idx + 3] = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put image data
|
||||||
|
ctx.putImageData(imageData, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
// Update all cells
|
||||||
|
for (const cell of this.cells) {
|
||||||
|
cell.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Occasionally rebuild diagram
|
||||||
|
if (Math.random() < params.motion * 0.02) {
|
||||||
|
this.buildDiagram();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update connections
|
||||||
|
if (params.connectedness > 0.2) {
|
||||||
|
for (let i = 0; i < this.cells.length; i++) {
|
||||||
|
for (let j = i + 1; j < this.cells.length; j++) {
|
||||||
|
const d = Math.sqrt(
|
||||||
|
Math.pow(this.cells[i].x - this.cells[j].x, 2) +
|
||||||
|
Math.pow(this.cells[i].y - this.cells[j].y, 2)
|
||||||
|
);
|
||||||
|
if (d < 100 * params.connectedness) {
|
||||||
|
this.cells[i].connect(this.cells[j]);
|
||||||
|
this.cells[j].connect(this.cells[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
draw() {
|
||||||
|
// Clear with subtle trail effect
|
||||||
|
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
// Draw all cells
|
||||||
|
for (const cell of this.cells) {
|
||||||
|
cell.draw(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw some random connections
|
||||||
|
if (params.connectedness > 0.5) {
|
||||||
|
ctx.strokeStyle = `rgba(255, 255, 255, ${0.05 + params.connectedness * 0.1})`;
|
||||||
|
ctx.lineWidth = 0.5;
|
||||||
|
ctx.beginPath();
|
||||||
|
for (let i = 0; i < this.cells.length; i++) {
|
||||||
|
for (let j = i + 1; j < Math.min(i + 5, this.cells.length); j++) {
|
||||||
|
const cell1 = this.cells[i];
|
||||||
|
const cell2 = this.cells[j];
|
||||||
|
if (Math.random() < params.connectedness) {
|
||||||
|
ctx.moveTo(cell1.x, cell1.y);
|
||||||
|
ctx.lineTo(cell2.x, cell2.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animation
|
||||||
|
let voronoi = new Voronoi();
|
||||||
|
|
||||||
|
function animate() {
|
||||||
|
voronoi.update();
|
||||||
|
voronoi.draw();
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
animate();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
Loading…
Add table
Reference in a new issue