Quasicrystal Void - Penrose Lattice Projection
Generated by GridFlow AI | Tags: quasicrystal, penrose-tiling, sacred-geometry, lattice-projection, hyper-dimensional, recursive-subdivision, 5-fold-symmetry, generative-art
💡 AI 提示词
A Penrose tiling quasicrystal lattice rendered with recursive 3D-to-2D projection techniques, featuring bright luminous lines against deep charcoal fog, sacred geometry aesthetic, metaphysical and hallucinatory quality suggesting alien artifact or consciousness simulation.🔧 核心算法要点
- Penrose tiling subdivision algorithm using kite and dart (thick/thin rhombus) inflation rules with 5-fold rotational symmetry
- 5D hyperplane projection method to create quasicrystal interference patterns through multiple angular projections
- Multi-layer compositing with three offscreen buffers: fog volume, quasicrystal texture, and tile lattice
- Pixel-level rendering using loadPixels/updatePixels for procedural fog density and interference calculations
- Recursive subdivision with generation tracking and depth-based stroke weight modulation
- Point-to-polygon and point-to-line distance calculations for geometric influence fields
- Screen and additive blend modes for luminous bloom effects on metallic line work
🎨 原始代码
var sketch = function(p) {
var tileBuffer, lineBuffer, fogBuffer;
var tiles = [];
var time = 0;
var mode = 0;
var mouseInfluence = 0;
var clickPulse = 0;
var keyToggle = false;
var lineOpacity = 255;
p.setup = function() {
var container = document.getElementById('p5-wrapper');
p.createCanvas(container.offsetWidth, container.offsetHeight).parent(container);
p.colorMode(p.RGB, 255, 255, 255, 255);
p.noStroke();
tileBuffer = p.createGraphics(p.width, p.height);
lineBuffer = p.createGraphics(p.width, p.height);
fogBuffer = p.createGraphics(p.width, p.height);
initTiles();
generateQuasicrystal();
};
function initTiles() {
tiles = [];
var scale = Math.min(p.width, p.height) * 0.012;
var c = p.createVector(p.width / 2, p.height / 2);
var dirs = [];
for (var i = 0; i < 5; i++) {
dirs.push(p5.Vector.fromAngle(i * p.TWO_PI / 5));
}
var halfEdge = scale * 3.5;
var angles = [0, p.TWO_PI / 5, 2 * p.TWO_PI / 5, 3 * p.TWO_PI / 5, 4 * p.TWO_PI / 5];
for (var a = 0; a < angles.length; a++) {
var rot = angles[a];
var v1 = p5.Vector.fromAngle(rot).mult(halfEdge);
var v2 = p5.Vector.fromAngle(rot + p.PI / 5).mult(halfEdge * 1.618);
var v3 = p5.Vector.fromAngle(rot + p.TWO_PI / 5).mult(halfEdge * 1.618);
var type = (a % 2 === 0) ? 'thick' : 'thin';
var tile = {
v: [
p5.Vector.add(c, v1),
p5.Vector.add(c, v2),
p5.Vector.add(c, v3)
],
type: type,
depth: 0,
generation: 0
};
var v4 = p5.Vector.add(c, p5.Vector.fromAngle(rot + p.PI / 5).mult(halfEdge * 1.618));
var v5 = p5.Vector.add(c, p5.Vector.fromAngle(rot - p.PI / 5).mult(halfEdge * 1.618));
if (a % 2 === 0) {
tile.v.push(v4);
tile.v.push(v5);
} else {
tile.v = [
p5.Vector.add(c, v1),
p5.Vector.add(c, v2),
p5.Vector.add(c, p5.Vector.fromAngle(rot - p.PI / 5).mult(halfEdge * 1.618))
];
tile.v.push(p5.Vector.add(c, p5.Vector.fromAngle(rot + p.PI / 5).mult(halfEdge * 1.618)));
}
tiles.push(tile);
}
}
function generateQuasicrystal() {
var maxGen = mode === 0 ? 4 : 3;
var newTiles = [];
for (var i = 0; i < tiles.length; i++) {
var tile = tiles[i];
if (tile.generation < maxGen) {
var subdivided = subdivideTile(tile);
newTiles = newTiles.concat(subdivided);
} else {
newTiles.push(tile);
}
}
tiles = newTiles;
}
function subdivideTile(tile) {
var result = [];
var vs = tile.v;
if (tile.type === 'thick') {
var a = vs[0], b = vs[1], c = vs[2], d = vs[3];
var ab = p5.Vector.lerp(a, b, 0.618);
var ac = p5.Vector.lerp(a, c, 0.618);
result.push({
v: [a, ab, ac],
type: 'thin',
generation: tile.generation + 1,
depth: tile.depth + 1
});
result.push({
v: [ab, b, c],
type: 'thick',
generation: tile.generation + 1,
depth: tile.depth + 1
});
result.push({
v: [ac, c, d],
type: 'thick',
generation: tile.generation + 1,
depth: tile.depth + 1
});
} else {
var a = vs[0], b = vs[1], c = vs[2], d = vs[3];
var ac = p5.Vector.lerp(a, c, 0.618);
var bd = p5.Vector.lerp(b, d, 0.618);
result.push({
v: [a, b, ac],
type: 'thick',
generation: tile.generation + 1,
depth: tile.depth + 1
});
result.push({
v: [ac, b, d],
type: 'thin',
generation: tile.generation + 1,
depth: tile.depth + 1
});
result.push({
v: [ac, d, c],
type: 'thin',
generation: tile.generation + 1,
depth: tile.depth + 1
});
}
return result;
}
function projectFrom5D(x, y, z) {
var angles = [0, p.TWO_PI / 5, 4 * p.TWO_PI / 5, 6 * p.TWO_PI / 5, 8 * p.TWO_PI / 5];
var projX = 0, projY = 0;
for (var i = 0; i < 5; i++) {
var cosA = p.cos(angles[i]);
var sinA = p.sin(angles[i]);
projX += (x * cosA + y * p.sin(angles[i] * 2) + z * cosA) * 0.5;
projY += (x * sinA + y * cosA + z * sinA) * 0.5;
}
return { x: projX, y: projY };
}
function drawLattice() {
lineBuffer.clear();
lineBuffer.background(0, 0);
var scale = Math.min(p.width, p.height) * 0.0012;
for (var i = 0; i < tiles.length; i++) {
var tile = tiles[i];
var vs = tile.v;
var mouseDist = p.dist(p.mouseX, p.mouseY,
(vs[0].x + vs[1].x + vs[2].x) / 3,
(vs[0].y + vs[1].y + vs[2].y) / 3);
var influence = p.map(p.constrain(mouseDist, 0, 300), 0, 300, 2, 0);
var baseBrightness = 200 + influence * 55;
var alpha = p.map(tile.generation, 0, 5, 255, 80);
if (tile.type === 'thick') {
var gold = p.map(tile.generation, 0, 5, 240, 180);
var warm = p.map(tile.generation, 0, 5, 200, 140);
lineBuffer.stroke(gold, warm * 0.85, 80, alpha);
lineBuffer.strokeWeight(p.map(tile.depth, 0, 5, 2.5, 0.8) + mouseInfluence * 0.5);
lineBuffer.beginShape();
for (var j = 0; j < vs.length; j++) {
lineBuffer.vertex(vs[j].x, vs[j].y);
}
lineBuffer.endShape(p.CLOSE);
if (tile.generation < 4) {
var cx = 0, cy = 0;
for (var j = 0; j < vs.length; j++) {
cx += vs[j].x;
cy += vs[j].y;
}
cx /= vs.length;
cy /= vs.length;
for (var j = 0; j < vs.length; j++) {
lineBuffer.line(cx, cy, vs[j].x, vs[j].y);
}
}
} else {
var silver = p.map(tile.generation, 0, 5, 220, 160);
var cool = p.map(tile.generation, 0, 5, 210, 150);
lineBuffer.stroke(silver, cool, 230, alpha * 0.7);
lineBuffer.strokeWeight(p.map(tile.depth, 0, 5, 1.8, 0.5) + mouseInfluence * 0.4);
lineBuffer.beginShape();
for (var j = 0; j < vs.length; j++) {
lineBuffer.vertex(vs[j].x, vs[j].y);
}
lineBuffer.endShape(p.CLOSE);
}
}
}
function drawFog() {
fogBuffer.loadPixels();
var step = 3;
for (var y = 0; y < fogBuffer.height; y += step) {
for (var x = 0; x < fogBuffer.width; x += step) {
var noiseVal = p.noise(x * 0.003 + time * 0.2, y * 0.003 + time * 0.15);
var fog = p.map(noiseVal, 0, 1, 25, 45);
var distFromCenter = p.dist(x, y, p.width / 2, p.height / 2);
var centerFade = p.map(distFromCenter, 0, p.width * 0.6, 15, 55);
var mouseEffect = p.map(p.dist(x, y, p.mouseX, p.mouseY), 0, 250, 0, 25);
var finalFog = fog + centerFade + mouseEffect;
finalFog = p.constrain(finalFog, 20, 80);
for (var dy = 0; dy < step && y + dy < fogBuffer.height; dy++) {
for (var dx = 0; dx < step && x + dx < fogBuffer.width; dx++) {
var idx = 4 * ((y + dy) * fogBuffer.width + (x + dx));
fogBuffer.pixels[idx] = finalFog;
fogBuffer.pixels[idx + 1] = finalFog;
fogBuffer.pixels[idx + 2] = finalFog;
fogBuffer.pixels[idx + 3] = 255;
}
}
}
}
fogBuffer.updatePixels();
}
function drawQuasicrystalTexture() {
tileBuffer.loadPixels();
var scale = Math.min(p.width, p.height) * 0.008;
for (var y = 0; y < tileBuffer.height; y += 2) {
for (var x = 0; x < tileBuffer.width; x += 2) {
var proj = projectFrom5D(x * 0.02, y * 0.02, time * 0.5);
var phase = proj.x + proj.y;
var interference = p.sin(phase * 3) * p.cos(phase * 2);
var brightness = p.map(interference, -1, 1, 0, 35);
var localBrightness = 0;
for (var i = 0; i < tiles.length; i++) {
var tile = tiles[i];
var dist = pointToPolygonDist(x, y, tile.v);
if (dist < scale * 2) {
localBrightness += p.map(dist, 0, scale * 2, 20, 0);
}
}
var totalBright = p.constrain(brightness + localBrightness, 0, 50);
for (var dy = 0; dy < 2 && y + dy < tileBuffer.height; dy++) {
for (var dx = 0; dx < 2 && x + dx < tileBuffer.width; dx++) {
var idx = 4 * ((y + dy) * tileBuffer.width + (x + dx));
tileBuffer.pixels[idx] = totalBright * 1.2;
tileBuffer.pixels[idx + 1] = totalBright * 1.1;
tileBuffer.pixels[idx + 2] = totalBright * 0.9;
tileBuffer.pixels[idx + 3] = 255;
}
}
}
}
tileBuffer.updatePixels();
}
function pointToPolygonDist(px, py, vertices) {
var minDist = Infinity;
for (var i = 0; i < vertices.length; i++) {
var j = (i + 1) % vertices.length;
var dist = pointToLineDist(px, py, vertices[i].x, vertices[i].y, vertices[j].x, vertices[j].y);
minDist = Math.min(minDist, dist);
}
return minDist;
}
function pointToLineDist(px, py, x1, y1, x2, y2) {
var A = px - x1;
var B = py - y1;
var C = x2 - x1;
var D = y2 - y1;
var dot = A * C + B * D;
var lenSq = C * C + D * D;
var param = -1;
if (lenSq !== 0) param = dot / lenSq;
var xx, yy;
if (param < 0) {
xx = x1;
yy = y1;
} else if (param > 1) {
xx = x2;
yy = y2;
} else {
xx = x1 + param * C;
yy = y1 + param * D;
}
var dx = px - xx;
var dy = py - yy;
return Math.sqrt(dx * dx + dy * dy);
}
function drawClickPulse() {
if (clickPulse > 0) {
var pulseRadius = (1 - clickPulse) * 400;
var alpha = clickPulse * 150;
p.push();
p.noFill();
p.stroke(255, 255, 240, alpha);
p.strokeWeight(3 * clickPulse);
p.beginShape();
for (var a = 0; a <= p.TWO_PI; a += 0.1) {
var r = pulseRadius;
var noiseR = r + p.noise(p.cos(a) * 2 + time, p.sin(a) * 2 + time) * 50 * clickPulse;
var vx = p.width / 2 + p.cos(a) * noiseR;
var vy = p.height / 2 + p.sin(a) * noiseR;
p.vertex(vx, vy);
}
p.endShape(p.CLOSE);
p.pop();
clickPulse *= 0.92;
if (clickPulse < 0.01) clickPulse = 0;
}
}
function drawGlowEffect() {
p.push();
p.blendMode(p.ADD);
p.noStroke();
var centerX = p.width / 2;
var centerY = p.height / 2;
for (var i = 0; i < tiles.length; i += 8) {
var tile = tiles[i];
var cx = 0, cy = 0;
for (var j = 0; j < tile.v.length; j++) {
cx += tile.v[j].x;
cy += tile.v[j].y;
}
cx /= tile.v.length;
cy /= tile.v.length;
var dist = p.dist(cx, cy, centerX, centerY);
var glowAlpha = p.map(dist, 0, p.width * 0.5, 30, 5) * p.sin(time * 2 + dist * 0.01) * 0.5 + 0.5;
p.fill(255, 255, 240, glowAlpha * 15);
p.ellipse(cx, cy, 20, 20);
}
p.pop();
}
p.draw = function() {
time += 0.02;
mouseInfluence = p.constrain(p.dist(p.mouseX, p.mouseY, p.width / 2, p.height / 2) / 300, 0, 1);
drawFog();
drawQuasicrystalTexture();
drawLattice();
drawGlowEffect();
p.background(35, 33, 38);
p.image(fogBuffer, 0, 0);
p.push();
p.blendMode(p.ADD);
p.image(tileBuffer, 0, 0);
p.pop();
p.push();
p.blendMode(p.SCREEN);
p.image(lineBuffer, 0, 0);
p.pop();
drawClickPulse();
p.push();
p.noStroke();
p.fill(255, 255, 255, 8);
var starCount = 50;
for (var i = 0; i < starCount; i++) {
var sx = (p.noise(i * 0.1, time * 0.3) * p.width + time * 20) % p.width;
var sy = p.noise(i * 0.1 + 100, time * 0.2) * p.height;
var size = p.noise(i * 0.2) * 3 + 1;
p.ellipse(sx, sy, size, size);
}
p.pop();
};
p.mouseMoved = function() {
mouseInfluence = p.constrain(p.dist(p.mouseX, p.mouseY, p.width / 2, p.height / 2) / 300, 0, 1);
};
p.mousePressed = function() {
clickPulse = 1;
initTiles();
generateQuasicrystal();
};
p.keyPressed = function() {
if (p.key === ' ' || p.key === 'Space') {
keyToggle = !keyToggle;
mode = (mode + 1) % 2;
initTiles();
generateQuasicrystal();
} else if (p.key === 'r' || p.key === 'R') {
initTiles();
generateQuasicrystal();
}
};
p.windowResized = function() {
var container = document.getElementById('p5-wrapper');
p.resizeCanvas(container.offsetWidth, container.offsetHeight);
tileBuffer.resizeCanvas(p.width, p.height);
lineBuffer.resizeCanvas(p.width, p.height);
fogBuffer.resizeCanvas(p.width, p.height);
initTiles();
generateQuasicrystal();
};
}; // p5 init stripped
✨ AI 艺术解读
This artwork manifests the mathematical beauty of quasicrystals through a hyper-dimensional projection lens, where 5-fold symmetry emerges from the void like ancient cosmic architecture. The luminous golden and silver lines trace the boundaries between order and chaos, representing the philosophical tension between periodicity and aperiodicity that defines quasicrystalline matter. The deep charcoal fog provides an infinite void backdrop, suggesting both cosmic vastness and inner consciousness. Each click regenerates the lattice, embodying the eternal emergence of structure from formlessness, while the floating stellar particles evoke the sacred connection between earthly geometry and celestial phenomena.
📝 补充说明
- Penrose subdivision uses the golden ratio (phi = 1.618) for recursive inflation of prototiles
- 5D projection basis vectors at angles 0, 72, 144, 216, 288 degrees create 5-fold symmetry
- Pixel-level rendering uses step size of 2-3 pixels for performance; quasicrystal texture uses step of 2
- Multiple blend modes (ADD, SCREEN) create luminous accumulation; order of compositing matters
- Mouse influence calculated as distance to tile centroids for local brightness modulation