252 lines
No EOL
8.8 KiB
HTML
252 lines
No EOL
8.8 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Neurameba · motd.social</title>
|
|
<style>
|
|
body { margin: 0; overflow: hidden; background: #000; color: #fff; font-family: monospace; }
|
|
#canvas { display: block; }
|
|
#attribution { position: absolute; bottom: 20px; right: 20px; font-size: 10px; opacity: 0.5; }
|
|
</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');
|
|
|
|
function resize() {
|
|
canvas.width = window.innerWidth;
|
|
canvas.height = window.innerHeight;
|
|
}
|
|
window.addEventListener('resize', resize);
|
|
resize();
|
|
|
|
const params = {
|
|
motion: 0.5,
|
|
density: 0.5,
|
|
complexity: 0.5,
|
|
connectedness: 0.5,
|
|
lifespan: 0.5,
|
|
pulse: 1.07,
|
|
tone: {
|
|
anger: 0.00,
|
|
sadness: 0.00,
|
|
curiosity: 0.70,
|
|
dryness: 0.80,
|
|
playfulness: 0.30,
|
|
tension: 0.00
|
|
}
|
|
};
|
|
|
|
let time = 0;
|
|
const points = [];
|
|
const voronoiCells = [];
|
|
|
|
// Initialize points
|
|
function initPoints() {
|
|
const count = Math.floor(params.density * 1000) + 100;
|
|
for (let i = 0; i < count; i++) {
|
|
points.push({
|
|
x: Math.random() * canvas.width,
|
|
y: Math.random() * canvas.height,
|
|
vx: (Math.random() - 0.5) * params.motion * 2,
|
|
vy: (Math.random() - 0.5) * params.motion * 2,
|
|
size: Math.random() * 3 + 1,
|
|
color: getColor()
|
|
});
|
|
}
|
|
}
|
|
|
|
function getColor() {
|
|
const hue = 180 + Math.sin(time * 0.01) * 30;
|
|
const saturation = 30 + params.tone.playfulness * 70;
|
|
const lightness = 60 + params.tone.dryness * 20;
|
|
return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
|
|
}
|
|
|
|
function updatePoints() {
|
|
points.forEach(p => {
|
|
p.x += p.vx;
|
|
p.y += p.vy;
|
|
|
|
if (p.x < 0 || p.x > canvas.width) p.vx *= -1;
|
|
if (p.y < 0 || p.y > canvas.height) p.vy *= -1;
|
|
});
|
|
|
|
// Generate voronoi diagram
|
|
voronoiCells.length = 0;
|
|
const cells = Delaunay.triangulate(points.map(p => [p.x, p.y]));
|
|
|
|
for (let i = 0; i < cells.length; i += 3) {
|
|
const a = points[cells[i]];
|
|
const b = points[cells[i+1]];
|
|
const c = points[cells[i+2]];
|
|
|
|
const centroid = {
|
|
x: (a.x + b.x + c.x) / 3,
|
|
y: (a.y + b.y + c.y) / 3
|
|
};
|
|
|
|
const edges = [];
|
|
edges.push({x1: a.x, y1: a.y, x2: b.x, y2: b.y});
|
|
edges.push({x1: b.x, y1: b.y, x2: c.x, y2: c.y});
|
|
edges.push({x1: c.x, y1: c.y, x2: a.x, y2: a.y});
|
|
|
|
voronoiCells.push({
|
|
centroid,
|
|
edges,
|
|
color: getColor()
|
|
});
|
|
}
|
|
}
|
|
|
|
function draw() {
|
|
// Fade effect for low lifespan
|
|
ctx.fillStyle = `rgba(0, 0, 0, ${1 - params.lifespan})`;
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
|
|
// Draw voronoi cells
|
|
voronoiCells.forEach(cell => {
|
|
ctx.strokeStyle = cell.color;
|
|
ctx.lineWidth = 1;
|
|
cell.edges.forEach(edge => {
|
|
ctx.beginPath();
|
|
ctx.moveTo(edge.x1, edge.y1);
|
|
ctx.lineTo(edge.x2, edge.y2);
|
|
ctx.stroke();
|
|
});
|
|
|
|
// Draw centroid if high complexity
|
|
if (params.complexity > 0.5) {
|
|
ctx.fillStyle = cell.color;
|
|
ctx.beginPath();
|
|
ctx.arc(cell.centroid.x, cell.centroid.y, 2, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
}
|
|
});
|
|
|
|
time += params.pulse;
|
|
}
|
|
|
|
function animate() {
|
|
updatePoints();
|
|
draw();
|
|
requestAnimationFrame(animate);
|
|
}
|
|
|
|
// Delaunay triangulation library
|
|
const Delaunay = {
|
|
triangulate: function(points) {
|
|
const edges = [];
|
|
const triangles = [];
|
|
|
|
// Bowyer-Watson algorithm
|
|
const superTriangle = this.createSuperTriangle(points);
|
|
triangles.push(superTriangle);
|
|
|
|
for (let i = 0; i < points.length; i++) {
|
|
const point = points[i];
|
|
const badTriangles = [];
|
|
|
|
for (let j = 0; j < triangles.length; j++) {
|
|
const triangle = triangles[j];
|
|
if (this.circumCircleContains(triangle, point)) {
|
|
badTriangles.push(triangle);
|
|
}
|
|
}
|
|
|
|
const polygon = [];
|
|
for (let j = 0; j < badTriangles.length; j++) {
|
|
const triangle = badTriangles[j];
|
|
for (let k = 0; k < 3; k++) {
|
|
const edge = {
|
|
p1: triangle[k],
|
|
p2: triangle[(k+1)%3]
|
|
};
|
|
let shared = false;
|
|
for (let m = 0; m < badTriangles.length; m++) {
|
|
if (m !== j) {
|
|
for (let n = 0; n < 3; n++) {
|
|
if (badTriangles[m][n] === edge.p1 && badTriangles[m][(n+1)%3] === edge.p2) {
|
|
shared = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (shared) break;
|
|
}
|
|
if (!shared) {
|
|
polygon.push(edge);
|
|
}
|
|
}
|
|
}
|
|
|
|
triangles.filter(t => !badTriangles.includes(t));
|
|
|
|
for (let j = 0; j < polygon.length; j++) {
|
|
const edge = polygon[j];
|
|
triangles.push([edge.p1, edge.p2, points.length]);
|
|
}
|
|
}
|
|
|
|
// Remove super triangle points
|
|
return triangles
|
|
.filter(t => t.indexOf(points.length) === -1 &&
|
|
t.indexOf(points.length+1) === -1 &&
|
|
t.indexOf(points.length+2) === -1)
|
|
.flat();
|
|
},
|
|
createSuperTriangle: function(points) {
|
|
let minX = points[0][0], maxX = minX;
|
|
let minY = points[0][1], maxY = minY;
|
|
|
|
for (let i = 0; i < points.length; i++) {
|
|
const p = points[i];
|
|
if (p[0] < minX) minX = p[0];
|
|
if (p[0] > maxX) maxX = p[0];
|
|
if (p[1] < minY) minY = p[1];
|
|
if (p[1] > maxY) maxY = p[1];
|
|
}
|
|
|
|
const dx = maxX - minX;
|
|
const dy = maxY - minY;
|
|
const deltaMax = Math.max(dx, dy) * 2;
|
|
const midX = (minX + maxX) / 2;
|
|
const midY = (minY + maxY) / 2;
|
|
|
|
return [
|
|
[midX - deltaMax, midY - deltaMax],
|
|
[midX, midY + deltaMax],
|
|
[midX + deltaMax, midY - deltaMax]
|
|
];
|
|
},
|
|
circumCircleContains: function(triangle, point) {
|
|
const A = triangle[0];
|
|
const B = triangle[1];
|
|
const C = triangle[2];
|
|
|
|
const ax = A[0], ay = A[1];
|
|
const bx = B[0], by = B[1];
|
|
const cx = C[0], cy = C[1];
|
|
const px = point[0], py = point[1];
|
|
|
|
const d = (ax*(by-cy) + bx*(cy-ay) + cx*(ay-by))*2;
|
|
if (d === 0) return false;
|
|
|
|
const x = ((ax*ax+ay*ay)*(by-cy) + (bx*bx+by*by)*(cy-ay) + (cx*cx+cy*cy)*(ay-by)) / d;
|
|
const y = ((ax*ax+ay*ay)*(cx-bx) + (bx*bx+by*by)*(ax-cx) + (cx*cx+cy*cy)*(bx-ax)) / d;
|
|
|
|
const radius = Math.sqrt((px-x)*(px-x) + (py-y)*(py-y));
|
|
const dist = Math.sqrt((ax-x)*(ax-x) + (ay-y)*(ay-y));
|
|
|
|
return radius >= dist;
|
|
}
|
|
};
|
|
|
|
initPoints();
|
|
animate();
|
|
</script>
|
|
</body>
|
|
</html> |