Hyperbolic Turing Automata
Generated by GridFlow AI | Tags: cellular-automata, hyperbolic-geometry, turing-patterns, neon-geometric, reaction-diffusion, non-euclidean, generative-art
💡 AI 提示词
Create a visually stunning p5.js artwork for Cellular Automata of Life-and-Death Turing Patterns using non-euclidean geometry simulation mapping coordinates through hyperbolic transformations. Visual style: neon geometric with ultra-saturated cyan and magenta intersecting lines on pitch black.🔧 核心算法要点
- Reaction-diffusion system (Gray-Scott model) simulates Turing pattern formation as the cellular automata mechanism
- Coordinates are transformed using Poincaré disk hyperbolic projection before sampling automata values
- Multiple offscreen buffers (bufferA, bufferB, compositeBuffer, hyperbolicBuffer) enable multi-layer compositing with different blend modes
- Per-pixel rendering with loadPixels/updatePixels creates glow effects and smooth gradients in the automata visualization
- Sacred geometry shapes (hexagonal patterns, sacred triangles, radiating lines) overlay the automata for mystical aesthetic
- Vignette overlay and chromatic aberration effects create depth and psychedelic atmosphere
- Mouse interaction distorts the automata field locally and triggers random parameter mutations on click
- Keyboard controls (C, R, M, G, H, Space) allow reset, parameter restoration, mode switching, and seeding
🎨 原始代码
var sketch = function(p) {
var bufferA, bufferB, compositeBuffer, hyperbolicBuffer;
var cols, rows;
var current, next;
var t = 0;
var mode = 0;
var mouseInfluence = 0;
var lastClickTime = 0;
var colorShift = 0;
var layerCount = 4;
var hyperbolicScale = 1.2;
var curvatureFactor = 0.7;
var cellSize = 4;
var dA = 1.0, dB = 0.5;
var feed = 0.055, kill = 0.062;
p.setup = function() {
var container = document.getElementById('p5-wrapper');
var w = container.offsetWidth;
var h = container.offsetHeight;
var cnv = p.createCanvas(w, h).parent(container);
cols = Math.floor(w / cellSize);
rows = Math.floor(h / cellSize);
bufferA = p.createGraphics(w, h);
bufferB = p.createGraphics(w, h);
compositeBuffer = p.createGraphics(w, h);
hyperbolicBuffer = p.createGraphics(w, h);
current = new Float32Array(cols * rows);
next = new Float32Array(cols * rows);
initializePattern();
};
function initializePattern() {
for (var i = 0; i < current.length; i++) {
current[i] = 1;
var x = i % cols;
var y = Math.floor(i / cols);
if (x > cols * 0.4 && x < cols * 0.6 && y > rows * 0.4 && y < rows * 0.6) {
current[i] = p.random(0, 0.5);
}
}
}
p.draw = function() {
p.background(0, 0, 0, 255);
t += 0.01;
colorShift += 0.005;
for (var k = 0; k < 8; k++) {
updateReactionDiffusion();
}
renderHyperbolicLayer();
renderTuringPattern();
renderGeometricOverlay();
compositeAllLayers();
applyFinalEffects();
};
function hyperbolicTransform(x, y, cx, cy) {
var dx = (x - cx) / p.width;
var dy = (y - cy) / p.height;
var r = Math.sqrt(dx * dx + dy * dy);
var theta = Math.atan2(dy, dx);
var newR = Math.tanh(Math.atanh(r) * curvatureFactor);
newR = Math.min(newR, 0.99);
var mx = cx + newR * Math.cos(theta) * p.width;
var my = cy + newR * Math.sin(theta) * p.height;
return { x: mx, y: my };
}
function updateReactionDiffusion() {
var mouseXNorm = p.mouseX / p.width;
var mouseYNorm = p.mouseY / p.height;
var distFromMouse = Math.sqrt(
Math.pow(mouseXNorm - 0.5, 2) + Math.pow(mouseYNorm - 0.5, 2)
);
mouseInfluence = Math.max(0, 1 - distFromMouse * 3);
for (var y = 1; y < rows - 1; y++) {
for (var x = 1; x < cols - 1; x++) {
var idx = y * cols + x;
var a = current[idx];
var b = current[idx + 1] || 0;
var laplace = 0;
laplace += current[idx] * -1;
laplace += current[idx - 1] * 0.2;
laplace += current[idx + 1] * 0.2;
laplace += current[y - 1 !== undefined ? (y - 1) * cols + x : idx] * 0.2;
laplace += current[y + 1 < rows ? (y + 1) * cols + x : idx] * 0.2;
laplace += current[y > 0 && x > 0 ? (y - 1) * cols + x - 1 : idx] * 0.05;
laplace += current[y > 0 && x < cols - 1 ? (y - 1) * cols + x + 1 : idx] * 0.05;
laplace += current[y < rows - 1 && x > 0 ? (y + 1) * cols + x - 1 : idx] * 0.05;
laplace += current[y < rows - 1 && x < cols - 1 ? (y + 1) * cols + x + 1 : idx] * 0.05;
var newA = a + (dA * laplace - a * b * b + feed * (1 - a)) * 0.9;
var newB = b + (dB * laplace + a * b * b - (kill + feed) * b) * 0.9;
if (mouseInfluence > 0.1 && p.mouseIsPressed) {
var px = x / cols;
var py = y / rows;
var localDist = Math.sqrt(Math.pow(px - mouseXNorm, 2) + Math.pow(py - mouseYNorm, 2));
if (localDist < 0.05) {
var influence = (0.05 - localDist) / 0.05;
newA = p.lerp(newA, 0.2, influence * 0.8);
newB = p.lerp(newB, 0.8, influence * 0.8);
}
}
next[idx] = Math.max(0, Math.min(1, newA));
}
}
var temp = current;
current = next;
next = temp;
}
function renderHyperbolicLayer() {
hyperbolicBuffer.background(0);
hyperbolicBuffer.loadPixels();
var step = 3;
for (var y = 0; y < p.height; y += step) {
for (var x = 0; x < p.width; x += step) {
var transformed = hyperbolicTransform(x, y, p.width / 2, p.height / 2);
var gridX = Math.floor(transformed.x / cellSize);
var gridY = Math.floor(transformed.y / cellSize);
if (gridX >= 0 && gridX < cols && gridY >= 0 && gridY < rows) {
var cellValue = current[gridY * cols + gridX];
var intensity = Math.floor(p.map(cellValue, 0, 1, 0, 255));
var hue1 = (200 + Math.sin(t * 2 + x * 0.01) * 40 + colorShift * 100) % 360;
var hue2 = (300 + Math.cos(t * 1.5 + y * 0.01) * 40 - colorShift * 80) % 360;
var r = p.color(180, 100, 100, 50);
var g = p.color(255, 0, 255, 30);
r = p.red(p.color(0, 255, 255));
g = p.green(p.color(255, 0, 255));
var color1 = p.color(0, 255, 255, intensity * 0.3);
var color2 = p.color(255, 0, 255, intensity * 0.3);
var blend = (Math.sin(transformed.x * 0.02 + t) + 1) * 0.5;
var finalR = p.lerp(p.red(color1), p.red(color2), blend);
var finalG = p.lerp(p.green(color1), p.green(color2), blend);
var finalB = p.lerp(p.blue(color1), p.blue(color2), blend);
for (var dy = 0; dy < step; dy++) {
for (var dx = 0; dx < step; dx++) {
var pxIdx = (y + dy) * p.width * 4 + (x + dx) * 4;
hyperbolicBuffer.pixels[pxIdx] = finalR;
hyperbolicBuffer.pixels[pxIdx + 1] = finalG;
hyperbolicBuffer.pixels[pxIdx + 2] = finalB;
hyperbolicBuffer.pixels[pxIdx + 3] = Math.min(255, intensity * 0.5);
}
}
}
}
}
hyperbolicBuffer.updatePixels();
}
function renderTuringPattern() {
bufferA.background(0);
bufferA.loadPixels();
var step = 2;
for (var y = 0; y < p.height; y += step) {
for (var x = 0; x < p.width; x += step) {
var transformed = hyperbolicTransform(x, y, p.width / 2, p.height / 2);
var gridX = Math.floor(transformed.x / cellSize);
var gridY = Math.floor(transformed.y / cellSize);
if (gridX >= 0 && gridX < cols && gridY >= 0 && gridY < rows) {
var cellValue = current[gridY * cols + gridX];
var a = cellValue;
var b = 1 - cellValue;
var cyan = p.color(0, 255, 255);
var magenta = p.color(255, 0, 255);
var cellColor = p.lerpColor(cyan, magenta, b);
var radius = step + b * step * 3;
for (var dy = 0; dy < radius && y + dy < p.height; dy++) {
for (var dx = 0; dx < radius && x + dx < p.width; dx++) {
var distFromCenter = Math.sqrt(dx * dx + dy * dy);
if (distFromCenter <= radius * 0.8) {
var pxIdx = (y + dy) * p.width * 4 + (x + dx) * 4;
var alpha = Math.floor(p.map(distFromCenter, 0, radius * 0.8, 200, 50) * a);
bufferA.pixels[pxIdx] = p.red(cellColor);
bufferA.pixels[pxIdx + 1] = p.green(cellColor);
bufferA.pixels[pxIdx + 2] = p.blue(cellColor);
bufferA.pixels[pxIdx + 3] = Math.min(255, alpha);
}
}
}
}
}
}
bufferA.updatePixels();
}
function renderGeometricOverlay() {
bufferB.background(0, 0, 0, 0);
bufferB.push();
bufferB.translate(p.width / 2, p.height / 2);
var numLayers = 12;
for (var i = 0; i < numLayers; i++) {
var layerProgress = i / numLayers;
var layerScale = 0.2 + layerProgress * 0.15;
var rotation = t * 0.1 * (i % 2 === 0 ? 1 : -1) + i * 0.3;
var layerAlpha = p.map(layerProgress, 0, 1, 0.4, 0.1);
var hue = (180 + i * 30 + colorShift * 50) % 360;
bufferB.stroke(hue, 100, 100, layerAlpha * 255);
bufferB.strokeWeight(1 + (1 - layerProgress) * 2);
bufferB.noFill();
drawHyperbolicCurve(layerScale, rotation, i);
}
bufferB.pop();
drawSacredGeometry();
}
function drawHyperbolicCurve(scale, rotation, layerIdx) {
bufferB.push();
bufferB.rotate(rotation);
var steps = 120;
var curvePoints = [];
for (var s = 0; s <= steps; s++) {
var tParam = (s / steps) * Math.PI * 2;
var baseX = Math.cos(tParam * (3 + layerIdx % 2)) * scale * p.width * 0.4;
var baseY = Math.sin(tParam * (4 + layerIdx % 3)) * scale * p.height * 0.4;
var hyperbolicR = Math.sqrt(baseX * baseX + baseY * baseY);
var hyperbolicTheta = Math.atan2(baseY, baseX);
var transformedR = Math.tanh(Math.atanh(hyperbolicR / (p.width * 0.5)) * 1.2);
var newX = transformedR * Math.cos(hyperbolicTheta);
var newY = transformedR * Math.sin(hyperbolicTheta);
var wobble = Math.sin(t * 3 + tParam * 6 + layerIdx) * 20;
curvePoints.push({
x: baseX + newX * p.width * 0.1 + Math.cos(tParam) * wobble,
y: baseY + newY * p.height * 0.1 + Math.sin(tParam) * wobble
});
}
bufferB.beginShape();
for (var i = 0; i < curvePoints.length; i++) {
bufferB.curveVertex(curvePoints[i].x, curvePoints[i].y);
}
bufferB.endShape();
bufferB.pop();
}
function drawSacredGeometry() {
var seed = Math.floor(t * 2);
var patternType = seed % 3;
bufferB.stroke(0, 255, 255, 30);
bufferB.strokeWeight(0.5);
if (patternType === 0) {
for (var ring = 0; ring < 8; ring++) {
var ringRadius = (ring + 1) * p.width * 0.08;
bufferB.ellipse(0, 0, ringRadius * 2, ringRadius * 2);
for (var dot = 0; dot < ring * 6; dot++) {
var angle = (dot / (ring * 6)) * Math.PI * 2 + t * 0.5;
var dotX = Math.cos(angle) * ringRadius;
var dotY = Math.sin(angle) * ringRadius;
bufferB.point(dotX, dotY);
}
}
} else if (patternType === 1) {
for (var line = 0; line < 24; line++) {
var lineAngle = (line / 24) * Math.PI * 2 + t * 0.3;
bufferB.line(0, 0,
Math.cos(lineAngle) * p.width * 0.45,
Math.sin(lineAngle) * p.height * 0.45);
}
for (var ring = 0; ring < 6; ring++) {
var radius = (ring + 1) * p.width * 0.07;
bufferB.ellipse(0, 0, radius * 2, radius * 2);
}
} else {
var triPoints = [];
for (var tri = 0; tri < 6; tri++) {
var triAngle = (tri / 6) * Math.PI * 2 - Math.PI / 2;
triPoints.push({
x: Math.cos(triAngle) * p.width * 0.35,
y: Math.sin(triAngle) * p.height * 0.35
});
}
bufferB.beginShape();
for (var i = 0; i <= 6; i++) {
var idx = i % 6;
var curveFactor = 0.3 + Math.sin(t * 2 + i) * 0.2;
var cx = triPoints[idx].x * curveFactor;
var cy = triPoints[idx].y * curveFactor;
bufferB.curveVertex(cx, cy);
}
bufferB.endShape();
}
}
function compositeAllLayers() {
compositeBuffer.background(0);
compositeBuffer.image(hyperbolicBuffer, 0, 0);
compositeBuffer.blendMode(p.ADD);
compositeBuffer.image(bufferA, 0, 0);
compositeBuffer.image(bufferB, 0, 0);
compositeBuffer.blendMode(p.BLEND);
}
function applyFinalEffects() {
p.image(compositeBuffer, 0, 0);
p.blendMode(p.SCREEN);
drawVignetteOverlay();
p.blendMode(p.ADD);
drawChromaticAberration();
p.blendMode(p.BLEND);
drawCentralVoid();
}
function drawVignetteOverlay() {
var vignetteSize = p.width * 1.5;
var maxDist = Math.sqrt(p.width * p.width + p.height * p.height) / 2;
p.noStroke();
for (var ring = 0; ring < 20; ring++) {
var ringRadius = maxDist * (ring / 20);
var nextRadius = maxDist * ((ring + 1) / 20);
var alpha = p.map(ring, 0, 20, 0, 180);
p.fill(0, 0, 0, alpha);
p.ellipse(p.width / 2, p.height / 2, ringRadius * 2, ringRadius * 2);
}
}
function drawChromaticAberration() {
var centerX = p.width / 2;
var centerY = p.height / 2;
for (var i = 0; i < 36; i++) {
var angle = (i / 36) * Math.PI * 2;
var radius = p.width * 0.05 + Math.sin(t * 3 + i * 0.5) * p.width * 0.02;
var ox = Math.cos(angle) * 5;
var oy = Math.sin(angle) * 5;
p.stroke(255, 0, 255, 20);
p.strokeWeight(1);
p.noFill();
p.ellipse(centerX + ox, centerY + oy, radius * 2, radius * 2);
p.stroke(0, 255, 255, 15);
p.ellipse(centerX - ox, centerY - oy, radius * 2, radius * 2);
}
}
function drawCentralVoid() {
var voidRadius = p.width * 0.08 + mouseInfluence * p.width * 0.05;
p.noStroke();
p.fill(0, 0, 0, 230);
p.ellipse(p.width / 2, p.height / 2, voidRadius * 2, voidRadius * 2);
p.stroke(0, 255, 255, 100);
p.strokeWeight(2);
p.noFill();
p.ellipse(p.width / 2, p.height / 2, voidRadius * 2.2, voidRadius * 2.2);
for (var ring = 0; ring < 5; ring++) {
var ringR = voidRadius + ring * 8;
var ringAlpha = 100 - ring * 20;
p.stroke(255, 0, 255, ringAlpha);
p.strokeWeight(1);
p.ellipse(p.width / 2, p.height / 2, ringR * 2, ringR * 2);
}
}
p.mouseMoved = function() {};
p.mousePressed = function() {
lastClickTime = p.millis();
curvatureFactor = 0.3 + p.random(0.3, 1.2);
feed = 0.04 + p.random(0, 0.03);
kill = 0.06 + p.random(0, 0.02);
initializePattern();
};
p.mouseReleased = function() {};
p.keyPressed = function() {
if (p.key === 'c' || p.key === 'C') {
initializePattern();
} else if (p.key === 'r' || p.key === 'R') {
curvatureFactor = 0.7;
feed = 0.055;
kill = 0.062;
} else if (p.key === 'm' || p.key === 'M') {
mode = (mode + 1) % 3;
if (mode === 0) {
dA = 1.0;
dB = 0.5;
} else if (mode === 1) {
dA = 0.8;
dB = 0.4;
} else {
dA = 1.2;
dB = 0.6;
}
} else if (p.key === 'g' || p.key === 'G') {
layerCount = layerCount === 4 ? 8 : 4;
} else if (p.key === 'h' || p.key === 'H') {
hyperbolicScale = 1.0 + Math.random() * 0.8;
} else if (p.key === ' ') {
for (var i = 0; i < current.length; i++) {
if (Math.random() < 0.3) {
current[i] = p.random(0, 0.3);
}
}
}
};
p.windowResized = function() {
var container = document.getElementById('p5-wrapper');
var w = container.offsetWidth;
var h = container.offsetHeight;
p.resizeCanvas(w, h);
bufferA.resizeCanvas(w, h);
bufferB.resizeCanvas(w, h);
compositeBuffer.resizeCanvas(w, h);
hyperbolicBuffer.resizeCanvas(w, h);
var oldCurrent = current;
var oldCols = cols;
var oldRows = rows;
cols = Math.floor(w / cellSize);
rows = Math.floor(h / cellSize);
current = new Float32Array(cols * rows);
next = new Float32Array(cols * rows);
for (var y = 0; y < rows; y++) {
for (var x = 0; x < cols; x++) {
var srcX = Math.floor(x * oldCols / cols);
var srcY = Math.floor(y * oldRows / rows);
if (srcX < oldCols && srcY < oldRows) {
current[y * cols + x] = oldCurrent[srcY * oldCols + srcX];
}
}
}
};
};
✨ AI 艺术解读
This artwork simulates the emergence of life and death patterns through a reaction-diffusion cellular automata mapped through hyperbolic non-Euclidean space. The viewer witnesses Turing patterns that would emerge in a curved universe — patterns that appear to breathe and evolve according to metaphysical laws. The central void represents the philosophical unknowable at the heart of existence, while the infinite recursive layers suggest consciousness itself. The ultra-saturated cyan and magenta palette creates a synesthetic experience where visual geometry becomes almost tangible sound.
📝 补充说明
- Hyperbolic transformation uses tanh(atanh(r)) mapping to create Poincaré disk projection of coordinates
- Cell size of 4 pixels with step size of 2-3 provides performance balance between detail and frame rate
- Eight iterations of reaction-diffusion update per frame maintain stable evolution speed
- Blend modes ADD and SCREEN create additive luminosity effects impossible with normal compositing
- The automata simulation runs at reduced resolution then scales for performance while maintaining visual quality