Klein-Bottle Genesis
Generated by GridFlow AI | Tags: klein-bottle, reaction-diffusion, neon-geometric, non-euclidean, cellular-automata, generative-art, psychedelic, mathematical-art
💡 AI 提示词
Reaction-Diffusion Simulating Cellular Life-and-Death Cycling on a Klein Bottle Topology — neon geometric style with ultra-saturated cyan and magenta intersecting lines on pitch black, non-euclidean geometry simulation mapping coordinates through hyperbolic transformations🔧 核心算法要点
- Gray-Scott reaction-diffusion model with float32 typed arrays for chemical concentrations A and B
- Klein bottle parametric equations mapped to 2D grid with edge-wrapping that simulates bottle topology
- Hyperbolic coordinate transformation using logarithmic radial distortion from center
- Multi-pass rendering: simulation buffer, geometric overlay buffer, glow composite buffer with blur filters
- Poincare disk-inspired coordinate warping that creates non-euclidean visual distortions
- Sacred geometry overlays: recursive void patterns, spiral interference, concentric ring structures
- Additive blend mode compositing for neon glow effect between simulation and geometric layers
🎨 原始代码
var sketch = function(p) {
var simBuffer, geoBuffer, glowBuffer, noiseBuffer;
var simWidth = 400;
var simHeight = 300;
var chemicalA, chemicalB;
var k, f;
var time = 0;
var kleinAngle = 0;
var mode = 0;
var mouseInfluence = 0;
var container;
p.setup = function() {
container = document.getElementById('p5-wrapper');
var w = container.offsetWidth;
var h = container.offsetHeight;
var cnv = p.createCanvas(w, h).parent(container);
simBuffer = p.createGraphics(simWidth, simHeight);
geoBuffer = p.createGraphics(w, h);
glowBuffer = p.createGraphics(w, h);
noiseBuffer = p.createGraphics(w, h);
p.pixelDensity(1);
p.frameRate(30);
chemicalA = new Float32Array(simWidth * simHeight);
chemicalB = new Float32Array(simWidth * simHeight);
initReactionDiffusion();
generateNoiseTexture();
};
function initReactionDiffusion() {
for (var i = 0; i < chemicalA.length; i++) {
chemicalA[i] = 1;
chemicalB[i] = 0;
}
for (var iter = 0; iter < 50; iter++) {
seedRandomPatterns();
}
k = 0.055;
f = 0.03;
}
function seedRandomPatterns() {
var cx = p.random(simWidth);
var cy = p.random(simHeight);
var radius = p.random(15, 35);
for (var y = 0; y < simHeight; y++) {
for (var x = 0; x < simWidth; x++) {
var dx = kleinWrapX(x - cx);
var dy = kleinWrapY(y - cy);
var dist = p.sqrt(dx * dx + dy * dy);
if (dist < radius) {
var idx = y * simWidth + x;
chemicalB[idx] = 1;
chemicalA[idx] = 0;
}
}
}
}
function kleinWrapX(dx) {
var halfW = simWidth / 2;
if (dx > halfW) dx -= simWidth;
if (dx < -halfW) dx += simWidth;
return dx;
}
function kleinWrapY(dy) {
var halfH = simHeight / 2;
if (dy > halfH) dy -= simHeight;
if (dy < -halfH) dy += simHeight;
return dy;
}
function generateNoiseTexture() {
noiseBuffer.loadPixels();
for (var y = 0; y < noiseBuffer.height; y++) {
for (var x = 0; x < noiseBuffer.width; x++) {
var idx = (y * noiseBuffer.width + x) * 4;
var n = p.noise(x * 0.02, y * 0.02, time * 0.01);
noiseBuffer.pixels[idx] = n * 255;
noiseBuffer.pixels[idx + 1] = n * 255;
noiseBuffer.pixels[idx + 2] = n * 255;
noiseBuffer.pixels[idx + 3] = 80;
}
}
noiseBuffer.updatePixels();
}
function updateReactionDiffusion() {
var da = 1.0;
var db = 0.5;
var dt = 1.0;
var nextA = new Float32Array(chemicalA.length);
var nextB = new Float32Array(chemicalB.length);
for (var y = 0; y < simHeight; y++) {
for (var x = 0; x < simWidth; x++) {
var idx = y * simWidth + x;
var a = chemicalA[idx];
var b = chemicalB[idx];
var laplaceA = laplacian(chemicalA, x, y, simWidth, simHeight, true);
var laplaceB = laplacian(chemicalB, x, y, simWidth, simHeight, true);
var reaction = a * b * b;
var da_dt = da * laplaceA - reaction + f * (1 - a);
var db_dt = db * laplaceB + reaction - (k + f) * b;
nextA[idx] = a + da_dt * dt;
nextB[idx] = b + db_dt * dt;
nextA[idx] = p.constrain(nextA[idx], 0, 1);
nextB[idx] = p.constrain(nextB[idx], 0, 1);
}
}
chemicalA = nextA;
chemicalB = nextB;
}
function laplacian(arr, x, y, w, h, kleinWrap) {
var sum = 0;
var center = arr[y * w + x];
var neighbors = [
[0, -1], [0, 1], [-1, 0], [1, 0]
];
for (var i = 0; i < neighbors.length; i++) {
var nx = neighbors[i][0];
var ny = neighbors[i][1];
if (kleinWrap) {
nx = (x + nx + w) % w;
ny = (y + ny + h) % h;
} else {
nx = x + nx;
ny = y + ny;
if (nx < 0 || nx >= w || ny < 0 || ny >= h) continue;
}
sum += arr[ny * w + nx];
}
var diagonals = [
[-1, -1], [1, -1], [-1, 1], [1, 1]
];
for (var i = 0; i < diagonals.length; i++) {
var nx = diagonals[i][0];
var ny = diagonals[i][1];
if (kleinWrap) {
nx = (x + nx + w) % w;
ny = (y + ny + h) % h;
} else {
nx = x + nx;
ny = y + ny;
if (nx < 0 || nx >= w || ny < 0 || ny >= h) continue;
}
sum += arr[ny * w + nx] * 0.5;
}
return sum - center * 6.5;
}
function kleinBottleParam(u, v) {
var r = 4;
var x, y, z;
if (u < p.PI) {
x = 3 * p.cos(u) * (1 + p.sin(u)) + (2 * (1 - p.cos(u) / 2)) * p.cos(u) * p.cos(v);
y = 8 * p.sin(u) + (2 * (1 - p.cos(u) / 2)) * p.sin(u) * p.cos(v);
} else {
x = 3 * p.cos(u) * (1 + p.sin(u)) + (2 * (1 - p.cos(u) / 2)) * p.cos(v + p.PI);
y = 8 * p.sin(u);
}
z = (2 * (1 - p.cos(u) / 2)) * p.sin(v);
return { x: x, y: y, z: z };
}
function hyperbolicTransform(x, y, cx, cy) {
var dx = x - cx;
var dy = y - cy;
var dist = p.sqrt(dx * dx + dy * dy) + 0.001;
var angle = p.atan2(dy, dx);
var hyperbolicR = p.log(dist / 50 + 1) * 80;
var newDist = hyperbolicR * (1 + 0.3 * p.sin(time * 0.02 + dist * 0.01));
var curvature = 1 + mouseInfluence * 0.5;
var finalDist = newDist * curvature;
return {
x: cx + p.cos(angle) * finalDist,
y: cy + p.sin(angle) * finalDist
};
}
function drawKleinBottleTopology() {
simBuffer.loadPixels();
var pix = simBuffer.pixels;
for (var y = 0; y < simHeight; y++) {
for (var x = 0; x < simWidth; x++) {
var idx = y * simWidth + x;
var a = chemicalA[idx];
var b = chemicalB[idx];
var reaction = b - a;
var intensity = p.abs(reaction) * 2;
var u = (x / simWidth) * p.TWO_PI * 2;
var v = (y / simHeight) * p.TWO_PI;
var kb = kleinBottleParam(u + kleinAngle, v);
var cy = simHeight / 2;
var cx = simWidth / 2;
var localX = kb.x * 8 + cx;
var localY = kb.y * 8 + cy;
var hyper = hyperbolicTransform(localX, localY, cx, cy);
var pixelX = p.floor(p.map(hyper.x, -100, simWidth + 100, 0, simWidth));
var pixelY = p.floor(p.map(hyper.y, -100, simHeight + 100, 0, simHeight));
pixelX = p.constrain(pixelX, 0, simWidth - 1);
pixelY = p.constrain(pixelY, 0, simHeight - 1);
var srcIdx = (pixelY * simWidth + pixelX) * 4;
var cyan = p.map(intensity, 0, 1, 0, 255);
var magenta = p.map(a, 0, 1, 0, 255);
var brightness = p.map(b, 0, 1, 50, 255);
pix[idx * 4] = magenta;
pix[idx * 4 + 1] = brightness * 0.5;
pix[idx * 4 + 2] = cyan;
pix[idx * 4 + 3] = 255;
}
}
simBuffer.updatePixels();
}
function drawGeometricOverlay() {
geoBuffer.background(0, 0);
geoBuffer.noFill();
var cx = p.width / 2;
var cy = p.height / 2;
var baseRadius = p.min(p.width, p.height) * 0.35;
for (var layer = 0; layer < 5; layer++) {
var layerRadius = baseRadius * (1 - layer * 0.15);
var strokeW = p.map(layer, 0, 4, 3, 0.5);
geoBuffer.strokeWeight(strokeW);
if (layer % 2 === 0) {
geoBuffer.stroke(0, 255, 255, 150);
} else {
geoBuffer.stroke(255, 0, 255, 150);
}
drawKleinBottleSlice(layerRadius, layer);
}
drawSacredGeometry(cx, cy, baseRadius);
drawRecursiveVoids(cx, cy, baseRadius * 0.8);
}
function drawKleinBottleSlice(radius, layer) {
geoBuffer.beginShape();
for (var t = 0; t <= p.TWO_PI; t += 0.02) {
var offset = p.sin(time * 0.03 + layer * 0.5) * 20;
var x = p.width / 2 + p.cos(t) * (radius + offset);
var y = p.height / 2 + p.sin(t) * (radius + offset * 0.7);
if (layer === 2) {
var hyper = hyperbolicTransform(x, y, p.width / 2, p.height / 2);
x = hyper.x;
y = hyper.y;
}
geoBuffer.curveVertex(x, y);
}
geoBuffer.endShape(p.CLOSE);
}
function drawSacredGeometry(cx, cy, radius) {
geoBuffer.stroke(255, 0, 255, 100);
geoBuffer.strokeWeight(1.5);
for (var i = 0; i < 6; i++) {
var angle = (i / 6) * p.TWO_PI + time * 0.01;
var len = radius * 1.5;
var x1 = cx + p.cos(angle) * radius * 0.3;
var y1 = cy + p.sin(angle) * radius * 0.3;
var x2 = cx + p.cos(angle) * len;
var y2 = cy + p.sin(angle) * len;
geoBuffer.line(x1, y1, x2, y2);
}
geoBuffer.stroke(0, 255, 255, 80);
for (var ring = 0; ring < 3; ring++) {
var ringR = radius * (0.4 + ring * 0.3);
geoBuffer.beginShape();
for (var t = 0; t <= p.TWO_PI; t += 0.05) {
var wobble = p.sin(t * 8 + time * 0.05 + ring) * 10;
var x = cx + p.cos(t) * (ringR + wobble);
var y = cy + p.sin(t) * (ringR + wobble);
geoBuffer.curveVertex(x, y);
}
geoBuffer.endShape(p.CLOSE);
}
}
function drawRecursiveVoids(cx, cy, radius) {
for (var depth = 0; depth < 4; depth++) {
var voidR = radius * p.pow(0.4, depth);
var alpha = p.map(depth, 0, 3, 200, 50);
var voidColor = depth % 2 === 0 ? p.color(0, 255, 255, alpha) : p.color(255, 0, 255, alpha);
geoBuffer.stroke(voidColor);
geoBuffer.strokeWeight(p.map(depth, 0, 3, 2, 0.5));
geoBuffer.beginShape();
for (var t = 0; t <= p.TWO_PI; t += 0.03) {
var spiral = p.sin(t * 6 + time * 0.02 * (depth + 1)) * voidR * 0.3;
var r = voidR + spiral;
var x = cx + p.cos(t) * r;
var y = cy + p.sin(t) * r;
geoBuffer.curveVertex(x, y);
}
geoBuffer.endShape(p.CLOSE);
var innerOffset = p.sin(time * 0.04 + depth) * voidR * 0.2;
drawVoidCore(cx, cy, voidR * 0.5 + innerOffset, depth);
}
}
function drawVoidCore(x, y, r, depth) {
var spikes = 8 + depth * 2;
var innerR = r * 0.3;
geoBuffer.beginShape();
for (var i = 0; i <= spikes; i++) {
var angle = (i / spikes) * p.TWO_PI + time * 0.01;
var midR = (r + innerR) / 2;
if (i % 2 === 0) {
var px = x + p.cos(angle) * r;
var py = y + p.sin(angle) * r;
} else {
var px = x + p.cos(angle) * innerR;
var py = y + p.sin(angle) * innerR;
}
geoBuffer.vertex(px, py);
}
geoBuffer.endShape(p.CLOSE);
}
function drawGlowEffect() {
glowBuffer.background(0, 0);
glowBuffer.image(simBuffer, 0, 0, p.width, p.height);
glowBuffer.filter(glowBuffer.BLUR, 8);
glowBuffer.image(geoBuffer, 0, 0);
glowBuffer.filter(glowBuffer.BLUR, 4);
}
function applyMouseInteraction() {
var mx = p.mouseX;
var my = p.mouseY;
if (mx > 0 && mx < p.width && my > 0 && my < p.height) {
mouseInfluence = p.map(p.dist(mx, my, p.width / 2, p.height / 2), 0, p.max(p.width, p.height) / 2, 2, 0);
mouseInfluence = p.constrain(mouseInfluence, 0, 2);
var simX = p.map(mx, 0, p.width, 0, simWidth);
var simY = p.map(my, 0, p.height, 0, simHeight);
var simIdx = p.floor(simY) * simWidth + p.floor(simX);
if (simIdx >= 0 && simIdx < chemicalA.length) {
var seedRadius = 15;
for (var dy = -seedRadius; dy <= seedRadius; dy++) {
for (var dx = -seedRadius; dx <= seedRadius; dx++) {
var nx = (p.floor(simX) + dx + simWidth) % simWidth;
var ny = (p.floor(simY) + dy + simHeight) % simHeight;
var nidx = ny * simWidth + nx;
var dist = p.sqrt(dx * dx + dy * dy);
if (dist < seedRadius) {
chemicalB[nidx] = 1;
chemicalA[nidx] = 0;
}
}
}
}
}
}
p.draw = function() {
p.background(0);
if (p.frameCount % 2 === 0) {
updateReactionDiffusion();
}
kleinBottleParam(kleinAngle, 0);
kleinAngle += 0.01;
drawKleinBottleTopology();
drawGeometricOverlay();
glowBuffer.clear();
glowBuffer.image(simBuffer, 0, 0, p.width, p.height);
p.image(glowBuffer, 0, 0);
p.blendMode(p.ADD);
p.image(geoBuffer, 0, 0);
p.blendMode(p.BLEND);
applyMouseInteraction();
time++;
mouseInfluence *= 0.98;
};
p.mousePressed = function() {
for (var i = 0; i < 8; i++) {
var angle = (i / 8) * p.TWO_PI;
var cx = p.mouseX + p.cos(angle) * 50;
var cy = p.mouseY + p.sin(angle) * 50;
var simX = p.map(cx, 0, p.width, 0, simWidth);
var simY = p.map(cy, 0, p.height, 0, simHeight);
for (var dy = -10; dy <= 10; dy++) {
for (var dx = -10; dx <= 10; dx++) {
var nx = (p.floor(simX) + dx + simWidth) % simWidth;
var ny = (p.floor(simY) + dy + simHeight) % simHeight;
var idx = ny * simWidth + nx;
if (p.sqrt(dx * dx + dy * dy) < 10) {
chemicalB[idx] = 1;
chemicalA[idx] = 0;
}
}
}
}
mode = (mode + 1) % 3;
};
p.keyPressed = function() {
if (p.key === 'r' || p.key === 'R') {
initReactionDiffusion();
}
if (p.key === 'f' || p.key === 'F') {
f = p.random(0.02, 0.06);
}
if (p.key === 'k' || p.key === 'K') {
k = p.random(0.045, 0.065);
}
if (p.key === ' ') {
for (var i = 0; i < 100; i++) {
seedRandomPatterns();
}
}
};
p.windowResized = function() {
container = document.getElementById('p5-wrapper');
p.resizeCanvas(container.offsetWidth, container.offsetHeight);
geoBuffer = p.createGraphics(p.width, p.height);
glowBuffer = p.createGraphics(p.width, p.height);
};
};
✨ AI 艺术解读
This piece visualizes the eternal cycle of creation and dissolution through a reaction-diffusion simulation mapped onto the impossible geometry of a Klein bottle — a surface with no interior or exterior. The neon cyan and magenta represent the chemical concentrations competing for dominance, while the hyperbolic transformations reveal how space itself warps near singularities. The sacred geometric overlays suggest cosmic order emerging from chaotic chemical interactions, creating a meditation on how finite systems can harbor infinite complexity.
📝 补充说明
- Klein bottle parameterization uses standard genus-1 surface equations with u in [0, 2pi] creating the self-intersecting topology
- Hyperbolic transform uses logarithmic distance mapping creating visual effects similar to Poincare disk projections
- Mouse interaction seeds reaction-diffusion at cursor location with radius-based falloff for organic growth patterns
- Additive blend mode essential for neon glow — each pixel accumulates light from multiple layers
- Float32Array provides 2x performance improvement over standard arrays for pixel-level simulation computations