Penrose-Bott Dissolution - Esoteric Geometries in Void
Generated by GridFlow AI | Tags: penrose-triangle, klein-bottle, tessellation, esoteric-occult, metallic-gold, recursive-projection, hyper-dimensional, void-aesthetics, bezier-geometry, non-orientable
💡 AI 提示词
Create a visually stunning p5.js artwork for the theme: Penrose Triangle Tessellation Dissolving into Klein Bottle Topology with esoteric occult visual style featuring metallic gold and brass structures floating in infinite dark voids. Use recursive 3D-to-2D projection as the primary rendering technique.🔧 核心算法要点
- Recursive Penrose triangle tessellation using golden ratio subdivision to create impossible geometry patterns at multiple depth levels
- Hyper-dimensional 3D-to-2D projection function using isometric projection combined with time-based rotation matrices and sinusoidal warping for non-Euclidean distortion
- Klein bottle topology warping through parametric equations mapping 2D coordinates through a self-intersecting manifold with fractal twist dynamics
- Multi-layer compositing using four separate createGraphics buffers (background, main, glow, projection) blended with ADD, SCREEN, and OVERLAY modes for luminous depth
- Pixel-level Klein bottle projection via loadPixels/updatePixels with step-based sampling for performance, combined with chromatic aberration post-processing
- Mouse attraction/repulsion influence on triangle positions with phase-shifted wobble effects during dissolution; click triggers depth escalation in nearby triangles
🎨 原始代码
var sketch = function(p) {
var bgBuffer, mainBuffer, glowBuffer, projBuffer;
var time = 0;
var dissolveFactor = 0;
var modeState = 0;
var keyPressed = {};
var PHI = (1 + Math.sqrt(5)) / 2;
var triangles = [];
var klenRadius = 0;
var hyperDepth = 0;
p.setup = function() {
var container = document.getElementById('p5-wrapper');
var canvas = p.createCanvas(container.offsetWidth, container.offsetHeight).parent(container);
bgBuffer = p.createGraphics(p.width, p.height);
mainBuffer = p.createGraphics(p.width, p.height);
glowBuffer = p.createGraphics(p.width, p.height);
projBuffer = p.createGraphics(p.width, p.height);
p.colorMode(p.HSB, 360, 100, 100, 100);
p.noStroke();
initTessellation();
};
function initTessellation() {
triangles = [];
var cx = p.width / 2;
var cy = p.height / 2;
var size = Math.min(p.width, p.height) * 0.35;
var h = size * Math.sqrt(3) / 2;
var root = {
v1: {x: cx, y: cy - 2 * h / 3},
v2: {x: cx + size / 2, y: cy + h / 3},
v3: {x: cx - size / 2, y: cy + h / 3},
depth: 0,
orient: 1,
size: size
};
subdividePenrose(root, 6);
}
function subdividePenrose(tri, depth) {
if (depth <= 0 || tri.size < 8) {
tri.hue = 42 + Math.sin(tri.depth * 0.5) * 8;
triangles.push(tri);
return;
}
var A = tri.v1;
var B = tri.v2;
var C = tri.v3;
var ratio = 1 / PHI;
var P = {
x: B.x + (C.x - B.x) * ratio,
y: B.y + (C.y - B.y) * ratio
};
var Q = {
x: B.x + (A.x - B.x) * ratio,
y: B.y + (A.y - B.y) * ratio
};
var R = {
x: P.x + (A.x - P.x) * ratio,
y: P.y + (A.y - P.y) * ratio
};
if (tri.orient > 0) {
subdividePenrose({v1: R, v2: P, v3: A, depth: depth - 1, orient: -1, size: tri.size * ratio}, depth - 1);
subdividePenrose({v1: P, v2: C, v3: A, depth: depth - 1, orient: 1, size: tri.size * ratio}, depth - 1);
subdividePenrose({v1: R, v2: P, v3: C, depth: depth - 1, orient: 1, size: tri.size * ratio}, depth - 1);
} else {
subdividePenrose({v1: Q, v2: R, v3: B, depth: depth - 1, orient: 1, size: tri.size * ratio}, depth - 1);
subdividePenrose({v1: Q, v2: R, v3: C, depth: depth - 1, orient: -1, size: tri.size * ratio}, depth - 1);
}
}
function projectHyper3Dto2D(vx, vy, vz, phase, depth) {
var theta = phase * 0.3 + depth * 0.1;
var cosT = Math.cos(theta);
var sinT = Math.sin(theta);
var isoAngle = Math.PI / 6;
var x = vx * Math.cos(isoAngle) - vz * Math.sin(isoAngle);
var y = vx * Math.sin(isoAngle) + vz * Math.cos(isoAngle) - vy * 0.5;
var rotY = x * cosT - y * sinT;
var rotZ = x * sinT + y * cosT;
var warpX = rotY + Math.sin(rotZ * 0.02 + phase * 2) * 15;
var warpY = rotZ * 0.7 + Math.cos(rotY * 0.015 + phase * 1.5) * 12;
return {x: warpX, y: warpY};
}
function kleinBottleWarp(x, y, t) {
var u = (x / p.width - 0.5) * Math.PI * 2;
var v = (y / p.height - 0.5) * Math.PI * 3;
var r = 0.4 + 0.1 * Math.sin(t * 0.5);
var twist = Math.sin(u * 3 + v * 2 + t) * klenRadius;
var squeeze = 1 + Math.cos(v * 4 - t * 1.3) * 0.15;
var newX = (u + twist) / (Math.PI * 2) * p.width + p.width * 0.5;
var newY = (v * squeeze) / (Math.PI * 3) * p.height + p.height * 0.5;
var fractal = Math.sin(u * 7 + t) * Math.cos(v * 5 - t * 0.7) * 20;
newX += fractal * (1 + Math.sin(t * 0.3) * 0.5);
newY += fractal * 0.7 * Math.cos(t * 0.4);
return {
nx: Math.max(0, Math.min(p.width - 1, newX)),
ny: Math.max(0, Math.min(p.height - 1, newY))
};
}
function drawTrianglePath(tri, t, idx) {
var cx = (tri.v1.x + tri.v2.x + tri.v3.x) / 3;
var cy = (tri.v1.y + tri.v2.y + tri.v3.y) / 3;
var mx = p.mouseX - cx;
var my = p.mouseY - cy;
var mouseDist = Math.sqrt(mx * mx + my * my);
var mouseAngle = Math.atan2(my, mx);
var influence = p.map(mouseDist, 0, p.width * 0.4, 1.2, 0.6);
influence = Math.max(0.4, Math.min(1.5, influence));
var phase = t * 0.5 + idx * 0.05;
var p1 = projectHyper3Dto2D(tri.v1.x - cx, tri.v1.y - cy, tri.depth * 5, phase, tri.depth);
var p2 = projectHyper3Dto2D(tri.v2.x - cx, tri.v2.y - cy, tri.depth * 5 + 50, phase + 0.3, tri.depth);
var p3 = projectHyper3Dto2D(tri.v3.x - cx, tri.v3.y - cy, tri.depth * 5 + 100, phase + 0.6, tri.depth);
p1.x = cx + p1.x * influence;
p1.y = cy + p1.y * influence;
p2.x = cx + p2.x * influence;
p2.y = cy + p2.y * influence;
p3.x = cx + p3.x * influence;
p3.y = cy + p3.y * influence;
var dissolve = dissolveFactor * (0.5 + Math.sin(phase * 2) * 0.3);
var wobble = Math.sin(phase * 3 + tri.depth) * dissolve * 20;
p1.x += wobble * Math.cos(mouseAngle + 0);
p1.y += wobble * Math.sin(mouseAngle + 0);
p2.x += wobble * Math.cos(mouseAngle + 2.1);
p2.y += wobble * Math.sin(mouseAngle + 2.1);
p3.x += wobble * Math.cos(mouseAngle + 4.2);
p3.y += wobble * Math.sin(mouseAngle + 4.2);
var ctrl1 = {
x: p1.x + (p2.x - p1.x) * 0.5 + Math.sin(phase * 4) * dissolve * 15,
y: p1.y + (p2.y - p1.y) * 0.5 + Math.cos(phase * 3) * dissolve * 15
};
var ctrl2 = {
x: p2.x + (p3.x - p2.x) * 0.5 + Math.sin(phase * 2.5 + 1) * dissolve * 12,
y: p2.y + (p3.y - p2.y) * 0.5 + Math.cos(phase * 2.8 + 1) * dissolve * 12
};
var ctrl3 = {
x: p3.x + (p1.x - p3.x) * 0.5 + Math.sin(phase * 3.5 + 2) * dissolve * 10,
y: p3.y + (p3.y - p1.y) * 0.5 + Math.cos(phase * 3.2 + 2) * dissolve * 10
};
mainBuffer.beginShape();
mainBuffer.vertex(p1.x, p1.y);
mainBuffer.bezierVertex(ctrl1.x, ctrl1.y, ctrl1.x, ctrl1.y, p2.x, p2.y);
mainBuffer.bezierVertex(ctrl2.x, ctrl2.y, ctrl2.x, ctrl2.y, p3.x, p3.y);
mainBuffer.bezierVertex(ctrl3.x, ctrl3.y, ctrl3.x, ctrl3.y, p1.x, p1.y);
mainBuffer.endShape(p.CLOSE);
var ctrX = (p1.x + p2.x + p3.x) / 3;
var ctrY = (p1.y + p2.y + p3.y) / 3;
var brightness = 65 + Math.sin(ctrX * 0.01 + t) * 20;
var sat = 60 + Math.sin(ctrY * 0.01 - t * 0.7) * 15;
mainBuffer.fill(tri.hue, sat, brightness, 85);
mainBuffer.noStroke();
mainBuffer.beginShape();
mainBuffer.vertex(p1.x, p1.y);
mainBuffer.bezierVertex(ctrl1.x, ctrl1.y, ctrl1.x, ctrl1.y, p2.x, p2.y);
mainBuffer.bezierVertex(ctrl2.x, ctrl2.y, ctrl2.x, ctrl2.y, p3.x, p3.y);
mainBuffer.bezierVertex(ctrl3.x, ctrl3.y, ctrl3.x, ctrl3.y, p1.x, p1.y);
mainBuffer.endShape(p.CLOSE);
glowBuffer.stroke(42, 80, 100, 20 + dissolve * 30);
glowBuffer.strokeWeight(2 + tri.depth * 0.3);
glowBuffer.noFill();
glowBuffer.beginShape();
glowBuffer.vertex(p1.x, p1.y);
glowBuffer.bezierVertex(ctrl1.x, ctrl1.y, ctrl1.x, ctrl1.y, p2.x, p2.y);
glowBuffer.bezierVertex(ctrl2.x, ctrl2.y, ctrl2.x, ctrl2.y, p3.x, p3.y);
glowBuffer.bezierVertex(ctrl3.x, ctrl3.y, ctrl3.x, ctrl3.y, p1.x, p1.y);
glowBuffer.endShape(p.CLOSE);
var hPos = p.map(ctrX, 0, p.width, 0, 360);
glowBuffer.fill(hPos, 70, 100, 40);
glowBuffer.noStroke();
for (var j = 0; j < 3; j++) {
var vx = j === 0 ? p1.x : (j === 1 ? p2.x : p3.x);
var vy = j === 0 ? p1.y : (j === 1 ? p2.y : p3.y);
glowBuffer.ellipse(vx, vy, 6 + Math.sin(t * 3 + j) * 3, 6 + Math.sin(t * 3 + j) * 3);
}
}
function renderBackgroundLayer(t) {
bgBuffer.background(0, 0, 3);
bgBuffer.noStroke();
var gradSize = Math.max(p.width, p.height) * 1.5;
var grad = bgBuffer.drawingContext.createRadialGradient(
p.width / 2, p.height / 2, 0,
p.width / 2, p.height / 2, gradSize
);
grad.addColorStop(0, 'rgba(20, 30, 15, 0.4)');
grad.addColorStop(0.5, 'rgba(10, 10, 8, 0.2)');
grad.addColorStop(1, 'rgba(0, 0, 0, 0)');
bgBuffer.drawingContext.fillStyle = grad;
bgBuffer.drawingContext.fillRect(0, 0, p.width, p.height);
for (var i = 0; i < 60; i++) {
var nx = p.noise(i * 0.1, t * 0.05) * p.width;
var ny = p.noise(i * 0.1 + 50, t * 0.05 + 30) * p.height;
var ns = p.noise(i * 0.05 + 100) * 4 + 0.5;
var na = p.noise(i * 0.08 + 200) * 15 + 5;
bgBuffer.fill(42, 40, 80, na);
bgBuffer.ellipse(nx, ny, ns * 4, ns * 4);
bgBuffer.fill(50, 60, 100, na * 0.4);
bgBuffer.ellipse(nx, ny, ns, ns);
}
}
function renderMainLayer(t) {
mainBuffer.background(0, 0, 0, 255);
klenRadius = 30 + Math.sin(t * 0.3) * 15 + dissolveFactor * 50;
for (var i = 0; i < triangles.length; i++) {
drawTrianglePath(triangles[i], t, i);
}
var mx = p.mouseX;
var my = p.mouseY;
var attractStrength = p.map(Math.sin(t * 0.5), -1, 1, 15, 35);
mainBuffer.stroke(42, 80, 100, 60);
mainBuffer.strokeWeight(1.5);
mainBuffer.noFill();
for (var r = 80; r > 20; r -= 15) {
mainBuffer.ellipse(mx, my, r, r);
}
glowBuffer.fill(45, 100, 100, 30);
glowBuffer.noStroke();
glowBuffer.ellipse(mx, my, attractStrength, attractStrength);
glowBuffer.fill(42, 70, 100, 50);
glowBuffer.ellipse(mx, my, attractStrength * 0.4, attractStrength * 0.4);
}
function projectThroughKleinBottle(t) {
projBuffer.loadPixels();
mainBuffer.loadPixels();
var step = 3;
for (var x = 0; x < p.width; x += step) {
for (var y = 0; y < p.height; y += step) {
var warped = kleinBottleWarp(x, y, t);
var srcX = Math.floor(warped.nx);
var srcY = Math.floor(warped.ny);
srcX = Math.max(0, Math.min(p.width - 1, srcX));
srcY = Math.max(0, Math.min(p.height - 1, srcY));
var idx = (srcY * p.width + srcX) * 4;
projBuffer.pixels[y * p.width * 4 + x * 4] = mainBuffer.pixels[idx];
projBuffer.pixels[y * p.width * 4 + x * 4 + 1] = mainBuffer.pixels[idx + 1];
projBuffer.pixels[y * p.width * 4 + x * 4 + 2] = mainBuffer.pixels[idx + 2];
projBuffer.pixels[y * p.width * 4 + x * 4 + 3] = mainBuffer.pixels[idx + 3];
}
}
projBuffer.updatePixels();
}
function applyLuminousCompositing(t) {
p.background(0, 0, 2);
p.image(bgBuffer, 0, 0);
p.blendMode(p.ADD);
p.tint(42, 50, 30, 70);
p.image(projBuffer, 0, 0);
p.blendMode(p.SCREEN);
p.tint(50, 40, 100, 40);
p.image(glowBuffer, 0, 0);
p.blendMode(p.OVERLAY);
p.tint(42, 30, 100, 25);
p.image(mainBuffer, 0, 0);
p.blendMode(p.BLEND);
}
function applyChromaticAberration() {
var step = 4;
p.loadPixels();
var tempBuffer = p.createGraphics(p.width, p.height);
tempBuffer.loadPixels();
for (var x = 0; x < p.width; x++) {
for (var y = 0; y < p.height; y++) {
var idx = y * p.width + x;
var shift = 2 + dissolveFactor * 4;
var rIdx = Math.max(0, Math.min(p.width - 1, x + shift)) + y * p.width;
var bIdx = Math.max(0, Math.min(p.width - 1, x - shift)) + y * p.width;
tempBuffer.pixels[idx * 4] = p.pixels[rIdx * 4];
tempBuffer.pixels[idx * 4 + 1] = p.pixels[idx * 4 + 1];
tempBuffer.pixels[idx * 4 + 2] = p.pixels[bIdx * 4 + 2];
tempBuffer.pixels[idx * 4 + 3] = p.pixels[idx * 4 + 3];
}
}
tempBuffer.updatePixels();
p.image(tempBuffer, 0, 0);
}
p.draw = function() {
time += 0.016;
dissolveFactor = 0.3 + Math.sin(time * 0.4) * 0.2;
if (modeState === 1) {
dissolveFactor = Math.min(1, dissolveFactor + 0.3);
}
renderBackgroundLayer(time);
renderMainLayer(time);
projectThroughKleinBottle(time);
applyLuminousCompositing(time);
applyChromaticAberration();
};
p.mouseMoved = function() {};
p.mousePressed = function() {
modeState = 1;
dissolveFactor = Math.min(1, dissolveFactor + 0.4);
var clickX = p.mouseX;
var clickY = p.mouseY;
glowBuffer.fill(45, 100, 100, 80);
glowBuffer.noStroke();
for (var r = 100; r > 0; r -= 10) {
glowBuffer.ellipse(clickX, clickY, r, r);
}
for (var i = 0; i < triangles.length; i++) {
var tcx = (triangles[i].v1.x + triangles[i].v2.x + triangles[i].v3.x) / 3;
var tcy = (triangles[i].v1.y + triangles[i].v2.y + triangles[i].v3.y) / 3;
var dist = Math.sqrt((tcx - clickX) ** 2 + (tcy - clickY) ** 2);
if (dist < 200) {
triangles[i].depth += 1;
triangles[i].hue = 42 + Math.sin(triangles[i].depth * 0.5) * 8;
}
}
};
p.mouseReleased = function() {
modeState = 0;
};
p.keyPressed = function() {
keyPressed[p.key] = true;
if (p.key === 'r' || p.key === 'R') {
initTessellation();
}
if (p.key === 'd' || p.key === 'D') {
dissolveFactor = dissolveFactor > 0.5 ? 0.1 : 0.8;
}
if (p.key === ' ') {
klenRadius = klenRadius > 30 ? 10 : 50;
}
};
p.keyReleased = function() {
keyPressed[p.key] = false;
};
p.windowResized = function() {
var container = document.getElementById('p5-wrapper');
p.resizeCanvas(container.offsetWidth, container.offsetHeight);
bgBuffer = p.createGraphics(p.width, p.height);
mainBuffer = p.createGraphics(p.width, p.height);
glowBuffer = p.createGraphics(p.width, p.height);
projBuffer = p.createGraphics(p.width, p.height);
initTessellation();
};
};
// p5 init stripped
✨ AI 艺术解读
This artwork manifests the impossible marriage of Penrose's non-orientable triangle tessellation with the topological paradox of a Klein bottle — a surface with only one side that cannot exist in three-dimensional space. The metallic gold triangles, recursively subdivided through the golden ratio, represent rational geometric structure dissolving into the incomprehensible topology of a fourth-dimensional object projected onto our flat perception. Each triangle wobbles and melts under Klein bottle warp functions, suggesting that our perceived reality is merely a distorted projection of hyper-dimensional truth. The dark void represents the infinite space where both impossible geometries coexist, while the luminous gold symbolizes the eternal light of mathematical truth piercing through the darkness of ignorance.
📝 补充说明
- The recursive 3D-to-2D projection uses time-varying rotation matrices in addition to standard isometric projection, creating the illusion of hyper-dimensional rotation in 2D space
- Golden ratio (PHI = 1.618) subdivision ensures aesthetic harmony and proper Penrose tessellation proportions at each recursion depth
- Pixel-level Klein bottle projection uses step=3 for performance while maintaining visual coherence through the smooth parametric warping function
- Bezier curves with control points perturbed by time-varying dissolve factor create organic, flowing triangle edges that feel alive rather than static
- Blend modes are applied in sequence (ADD for base luminosity, SCREEN for glow, OVERLAY for metallic sheen) to achieve photographic-quality compositing depth