Klein-Bottle Fluid Dynamics - Recursive Non-Orientable Flow

📅 April 25, 2026 🏷️ art
klein-bottle fluid-dynamics 4d-projection sacred-geometry recursive-rendering non-orientable-surface volumetric-fog generative-art pending-review
Generated by GridFlow AI | Tags: klein-bottle, fluid-dynamics, 4d-projection, sacred-geometry, recursive-rendering, non-orientable-surface, volumetric-fog, generative-art

💡 AI 提示词

Klein Bottle Fluid Dynamics rendered with recursive 4D-to-2D stereographic projection, sacred geometry golden ratio spirals, luminous white lines against deep charcoal fog atmosphere, volumetric pixel-level rendering, multi-layer compositing with ADD and SCREEN blend modes, flowing organic curveVertex patterns simulating non-Euclidean fluid flow across a non-orientable surface

🔧 核心算法要点

  1. 4D Klein bottle parametric surface generation using trigonometric formulas across U and V resolution grids
  2. Recursive multi-axis 4D rotation combining rotations in XW, XY, YZ, and ZW planes with time-based animation
  3. Stereographic projection pipeline: 4D to 3D perspective division, then 3D to 2D screen coordinates with dual perspective transforms
  4. Pixel-level fog atmosphere rendering using Perlin noise volumetric density calculations with sacred geometry grid overlay
  5. Fluid dynamics simulation applying vortex angle calculations, spiral factors based on golden ratio, and mouse/burst interaction forces
  6. Multi-layer compositing: fog buffer as base, fluid simulation with SCREEN blend, geometry with ADD blend, glow layer with SCREEN blend, and vignette overlay

🎨 原始代码

var sketch = function(p) {
  var container, mainCanvas;
  var fogBuffer, geoBuffer, glowBuffer, fluidBuffer;
  var kleinPoints = [];
  var flowField = [];
  var time = 0;
  var mouseInfluence = 0;
  var clickBurst = [];
  var projectionMode = 0;
  var showSacredGrid = true;
  var PHI = (1 + Math.sqrt(5)) / 2;
  var MAX_POINTS = 800;
  var FOG_STEPS = 120;
  
  p.setup = function() {
    container = document.getElementById('p5-wrapper');
    p.createCanvas(container.offsetWidth, container.offsetHeight).parent(container);
    p.colorMode(p.RGB, 255, 255, 255, 1);
    p.noStroke();
    
    fogBuffer = p.createGraphics(p.width, p.height);
    geoBuffer = p.createGraphics(p.width, p.height);
    glowBuffer = p.createGraphics(p.width, p.height);
    fluidBuffer = p.createGraphics(p.width, p.height);
    
    fogBuffer.colorMode(p.RGB, 255, 255, 255, 1);
    glowBuffer.colorMode(p.RGB, 255, 255, 255, 1);
    fluidBuffer.colorMode(p.RGB, 255, 255, 255, 1);
    
    initializeKleinBottle();
    initializeFlowField();
    renderFogAtmosphere();
  };
  
  function initializeKleinBottle() {
    kleinPoints = [];
    var uResolution = 48;
    var vResolution = 32;
    
    for (var i = 0; i <= uResolution; i++) {
      for (var j = 0; j <= vResolution; j++) {
        var u = (i / uResolution) * p.TWO_PI;
        var v = (j / vResolution) * p.TWO_PI * 2;
        
        var radius = 100 + 40 * Math.cos(v / 2);
        var x4d = radius * Math.cos(u) + 60 * Math.sin(2 * u) * Math.cos(v);
        var y4d = radius * Math.sin(u) + 60 * Math.sin(2 * u) * Math.sin(v);
        var z4d = radius * Math.cos(v / 2) + 40 * Math.sin(4 * u);
        var w4d = 40 * Math.sin(v / 2);
        
        kleinPoints.push({
          x4: x4d, y4: y4d, z4: z4d, w4: w4d,
          u: u, v: v,
          baseX: x4d, baseY: y4d, baseZ: z4d, baseW: w4d,
          phase: p.random(p.TWO_PI),
          spiral: p.floor(p.random(3))
        });
      }
    }
  }
  
  function initializeFlowField() {
    flowField = [];
    var cols = Math.ceil(p.width / 12);
    var rows = Math.ceil(p.height / 12);
    
    for (var i = 0; i < cols * rows; i++) {
      flowField.push({
        angle: p.random(p.TWO_PI),
        speed: p.random(0.5, 2),
        phase: p.random(p.TWO_PI),
        magnitude: p.random(20, 60)
      });
    }
  }
  
  function project4Dto2D(x4, y4, z4, w4, projectionIndex) {
    var rotXW = time * 0.3 + projectionIndex * 0.1;
    var rotXY = time * 0.2;
    var rotYZ = time * 0.15;
    var rotZW = time * 0.25;
    
    var x = x4;
    var y = y4;
    var z = z4;
    var w = w4;
    
    var cosXW = Math.cos(rotXW), sinXW = Math.sin(rotXW);
    var xNew = x * cosXW - w * sinXW;
    var wNew = x * sinXW + w * cosXW;
    x = xNew; w = wNew;
    
    var cosXY = Math.cos(rotXY), sinXY = Math.sin(rotXY);
    var xTemp = x * cosXY - y * sinXY;
    var yTemp = x * sinXY + y * cosXY;
    x = xTemp; y = yTemp;
    
    var cosYZ = Math.cos(rotYZ), sinYZ = Math.sin(rotYZ);
    var yTemp2 = y * cosYZ - z * sinYZ;
    var zTemp = y * sinYZ + z * cosYZ;
    y = yTemp2; z = zTemp;
    
    var cosZW = Math.cos(rotZW), sinZW = Math.sin(rotZW);
    var zNew = z * cosZW - w * sinZW;
    var wNew2 = z * sinZW + w * cosZW;
    z = zNew; w = wNew2;
    
    var perspective4D = 2 / (2 + w * 0.008);
    var perspective3D = 2 / (2 + z * 0.005);
    
    var x3 = x * perspective4D;
    var y3 = y * perspective4D;
    var z3 = w * perspective4D;
    
    var screenX = x3 * perspective3D;
    var screenY = y3 * perspective3D;
    
    var centerX = p.width / 2 + (p.mouseX - p.width / 2) * 0.3;
    var centerY = p.height / 2 + (p.mouseY - p.height / 2) * 0.3;
    
    return {
      x: centerX + screenX * 1.2,
      y: centerY + screenY * 1.2,
      z: z3,
      depth: perspective4D * perspective3D
    };
  }
  
  function applyFluidDynamics(point, index) {
    var fluidNoise = p.noise(
      point.baseX * 0.003 + time * 0.4,
      point.baseY * 0.003 + index * 0.01,
      point.baseZ * 0.003
    );
    
    var vortexAngle = fluidNoise * p.TWO_PI * 2;
    var spiralFactor = Math.sin(point.v * PHI + time * 0.5) * 15;
    
    var centerX = p.width / 2;
    var centerY = p.height / 2;
    var distFromCenter = Math.sqrt(
      Math.pow(point.baseX, 2) + Math.pow(point.baseY, 2)
    );
    
    var flowStrength = p.map(distFromCenter, 0, 200, 0.8, 0.3);
    
    point.x4 = point.baseX + Math.cos(vortexAngle) * spiralFactor * flowStrength;
    point.y4 = point.baseY + Math.sin(vortexAngle) * spiralFactor * flowStrength;
    point.z4 = point.baseZ + Math.sin(point.u * 3 + time) * 10;
    point.w4 = point.baseW + Math.cos(point.v * 2 + time * 0.7) * 8;
    
    if (mouseInfluence > 0) {
      var mouseX4d = (p.mouseX - centerX) * 2;
      var mouseY4d = (p.mouseY - centerY) * 2;
      var distToMouse = Math.sqrt(
        Math.pow(point.x4 - mouseX4d, 2) +
        Math.pow(point.y4 - mouseY4d, 2)
      );
      
      if (distToMouse < 150) {
        var force = (150 - distToMouse) / 150 * mouseInfluence * 30;
        var angle = Math.atan2(point.y4 - mouseY4d, point.x4 - mouseX4d);
        point.x4 += Math.cos(angle) * force;
        point.y4 += Math.sin(angle) * force;
      }
    }
    
    for (var i = 0; i < clickBurst.length; i++) {
      var burst = clickBurst[i];
      var distToBurst = Math.sqrt(
        Math.pow(point.x4 - burst.x, 2) +
        Math.pow(point.y4 - burst.y, 2)
      );
      
      if (distToBurst < burst.radius) {
        var force = burst.strength * (burst.radius - distToBurst) / burst.radius;
        var angle = Math.atan2(point.y4 - burst.y, point.x4 - burst.x);
        point.x4 += Math.cos(angle) * force;
        point.y4 += Math.sin(angle) * force;
      }
    }
  }
  
  function renderFogAtmosphere() {
    fogBuffer.loadPixels();
    
    for (var y = 0; y < p.height; y += 3) {
      for (var x = 0; x < p.width; x += 3) {
        var centerX = p.width / 2;
        var centerY = p.height / 2;
        var dist = Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2));
        var maxDist = Math.sqrt(Math.pow(p.width / 2, 2) + Math.pow(p.height / 2, 2));
        
        var noiseVal = p.noise(x * 0.003 + time * 0.1, y * 0.003 + time * 0.08);
        var fogDensity = 0.02 + noiseVal * 0.03;
        var fogValue = p.map(dist, 0, maxDist, 0.95, 0.08);
        fogValue *= fogDensity * 40;
        
        var sacredNoise = p.noise(
          x * 0.01 * PHI, 
          y * 0.01 / PHI, 
          time * 0.2
        );
        
        var sacredPattern = 0;
        if (sacredNoise > 0.65 && showSacredGrid) {
          var gridX = (x % 50) / 50;
          var gridY = (y % 50) / 50;
          var distToLine = Math.min(gridX, gridY, 1 - gridX, 1 - gridY);
          if (distToLine < 0.08) {
            sacredPattern = p.map(distToLine, 0.08, 0, 0, 0.15);
          }
        }
        
        var finalValue = Math.min(255, fogValue * 255 + sacredPattern * 255);
        
        var index = (y * p.width + x) * 4;
        fogBuffer.pixels[index] = finalValue * 0.12;
        fogBuffer.pixels[index + 1] = finalValue * 0.12;
        fogBuffer.pixels[index + 2] = finalValue * 0.14;
        fogBuffer.pixels[index + 3] = 1;
      }
    }
    
    fogBuffer.updatePixels();
  }
  
  function drawSacredGeometry(buffer, centerX, centerY, radius) {
    buffer.push();
    buffer.translate(centerX, centerY);
    buffer.noFill();
    
    var phiSpirals = 5;
    for (var s = 0; s < phiSpirals; s++) {
      buffer.beginShape();
      for (var t = 0; t < 200; t++) {
        var angle = t * 0.1 + (s * p.TWO_PI / phiSpirals);
        var spiralRadius = radius * 0.3 + t * 0.8;
        var x = Math.cos(angle) * spiralRadius;
        var y = Math.sin(angle) * spiralRadius;
        var wobble = Math.sin(angle * 3 + time * 2) * 5;
        buffer.vertex(x + wobble, y + wobble);
      }
      buffer.endShape();
    }
    
    buffer.noFill();
    for (var r = 1; r <= 6; r++) {
      var goldenRadius = radius * 0.15 * Math.pow(PHI, r * 0.5);
      buffer.stroke(255, 255, 255, 0.03 + r * 0.02);
      buffer.strokeWeight(0.5);
      buffer.ellipse(0, 0, goldenRadius * 2, goldenRadius * 2);
      
      buffer.beginShape();
      for (var a = 0; a <= 12; a++) {
        var ang = a * p.TWO_PI / 12;
        var px = Math.cos(ang) * goldenRadius;
        var py = Math.sin(ang) * goldenRadius;
        buffer.curveVertex(px, py);
      }
      buffer.endShape(p.CLOSE);
    }
    
    buffer.pop();
  }
  
  function drawKleinSurface(buffer, projectedPoints) {
    buffer.push();
    
    var uResolution = 49;
    var vResolution = 33;
    
    for (var i = 0; i < uResolution; i++) {
      for (var j = 0; j < vResolution; j++) {
        var idx = i * vResolution + j;
        var idxRight = (i + 1) * vResolution + j;
        var idxDown = i * vResolution + (j + 1);
        var idxDiag = (i + 1) * vResolution + (j + 1);
        
        if (idx >= projectedPoints.length || idxRight >= projectedPoints.length ||
            idxDown >= projectedPoints.length || idxDiag >= projectedPoints.length) continue;
        
        var p1 = projectedPoints[idx];
        var p2 = projectedPoints[idxRight];
        var p3 = projectedPoints[idxDown];
        var p4 = projectedPoints[idxDiag];
        
        var avgDepth = (p1.depth + p2.depth + p3.depth + p4.depth) / 4;
        var alpha = p.map(avgDepth, 0.2, 1.2, 0.02, 0.12);
        
        var fluidPulse = Math.sin(time * 2 + i * 0.2 + j * 0.15) * 0.5 + 0.5;
        var luminosity = 0.3 + fluidPulse * 0.7;
        
        buffer.fill(255 * luminosity, 255 * luminosity, 255 * luminosity * 0.95, alpha);
        buffer.stroke(255, 255, 255, alpha * 1.5);
        buffer.strokeWeight(0.3);
        
        buffer.beginShape();
        buffer.vertex(p1.x, p1.y);
        buffer.vertex(p2.x, p2.y);
        buffer.vertex(p4.x, p4.y);
        buffer.vertex(p3.x, p3.y);
        buffer.endShape(p.CLOSE);
      }
    }
    
    buffer.pop();
  }
  
  function drawFlowingCurves(buffer, projectedPoints) {
    buffer.push();
    buffer.noFill();
    
    var uResolution = 49;
    var vResolution = 33;
    var curvePatterns = [0, 8, 16, 24, 32];
    
    for (var pattern = 0; pattern < curvePatterns.length; pattern++) {
      var step = curvePatterns[pattern];
      
      for (var i = 0; i < uResolution - step; i += step + 1) {
        buffer.beginShape();
        buffer.strokeWeight(0.5 + pattern * 0.3);
        
        var hueShift = pattern * 15;
        buffer.stroke(255 - hueShift * 0.3, 255 - hueShift * 0.2, 255, 0.15 - pattern * 0.02);
        
        for (var j = 0; j < vResolution; j++) {
          var idx = i * vResolution + j;
          if (idx < projectedPoints.length) {
            var pt = projectedPoints[idx];
            var fluidWarp = Math.sin(time * 3 + j * 0.3) * 3;
            buffer.curveVertex(pt.x + fluidWarp, pt.y + fluidWarp * 0.5);
          }
        }
        buffer.endShape();
      }
      
      for (var j = 0; j < vResolution; j += 4) {
        buffer.beginShape();
        buffer.strokeWeight(0.4);
        buffer.stroke(255, 255, 255, 0.08);
        
        for (var i = 0; i < uResolution; i++) {
          var idx = i * vResolution + j;
          if (idx < projectedPoints.length) {
            var pt = projectedPoints[idx];
            var warpX = Math.cos(time * 2 + i * 0.2) * 2;
            var warpY = Math.sin(time * 2.5 + i * 0.15) * 2;
            buffer.curveVertex(pt.x + warpX, pt.y + warpY);
          }
        }
        buffer.endShape();
      }
    }
    
    buffer.pop();
  }
  
  function renderGlowLayer(projectedPoints) {
    glowBuffer.clear();
    glowBuffer.loadPixels();
    
    for (var i = 0; i < projectedPoints.length; i += 3) {
      var pt = projectedPoints[i];
      var screenX = Math.floor(pt.x);
      var screenY = Math.floor(pt.y);
      
      var point = kleinPoints[i % kleinPoints.length];
      var pulse = Math.sin(time * 4 + point.phase) * 0.5 + 0.5;
      var glowSize = 8 + pulse * 12;
      
      for (var gy = -glowSize; gy < glowSize; gy += 2) {
        for (var gx = -glowSize; gx < glowSize; gx += 2) {
          var px = screenX + gx;
          var py = screenY + gy;
          
          if (px < 0 || px >= p.width || py < 0 || py >= p.height) continue;
          
          var dist = Math.sqrt(gx * gx + gy * gy);
          var glowIntensity = Math.max(0, 1 - dist / glowSize) * pulse * 0.4;
          
          var index = (py * p.width + px) * 4;
          glowBuffer.pixels[index] = Math.min(255, glowBuffer.pixels[index] + 255 * glowIntensity);
          glowBuffer.pixels[index + 1] = Math.min(255, glowBuffer.pixels[index + 1] + 255 * glowIntensity * 0.95);
          glowBuffer.pixels[index + 2] = Math.min(255, glowBuffer.pixels[index + 2] + 255 * glowIntensity * 0.9);
        }
      }
    }
    
    glowBuffer.updatePixels();
  }
  
  function renderFluidSimulation(buffer) {
    buffer.clear();
    buffer.loadPixels();
    
    var cols = Math.ceil(p.width / 8);
    var rows = Math.ceil(p.height / 8);
    
    for (var y = 0; y < p.height; y += 4) {
      for (var x = 0; x < p.width; x += 4) {
        var col = Math.floor(x / 8);
        var row = Math.floor(y / 8);
        var flowIdx = row * cols + col;
        
        if (flowIdx >= flowField.length) continue;
        
        var flow = flowField[flowIdx];
        var angle = flow.angle + time * flow.speed * 0.1;
        
        var noiseX = x * 0.005 + Math.cos(angle) * 0.5;
        var noiseY = y * 0.005 + Math.sin(angle) * 0.5;
        var noiseVal = p.noise(noiseX, noiseY, time * 0.3);
        
        var flowX = x + Math.cos(angle) * flow.magnitude * noiseVal;
        var flowY = y + Math.sin(angle) * flow.magnitude * noiseVal;
        
        var distFromCenter = Math.sqrt(
          Math.pow(flowX - p.width / 2, 2) + 
          Math.pow(flowY - p.height / 2, 2)
        );
        var vortexStrength = Math.sin(distFromCenter * 0.02 - time * 2) * 0.5 + 0.5;
        
        var intensity = noiseVal * vortexStrength * 0.15;
        
        var px = Math.floor(flowX);
        var py = Math.floor(flowY);
        
        if (px >= 0 && px < p.width && py >= 0 && py < p.height) {
          var index = (py * p.width + px) * 4;
          buffer.pixels[index] = Math.min(255, buffer.pixels[index] + 255 * intensity);
          buffer.pixels[index + 1] = Math.min(255, buffer.pixels[index + 1] + 255 * intensity * 0.9);
          buffer.pixels[index + 2] = Math.min(255, buffer.pixels[index + 2] + 255 * intensity * 0.85);
        }
      }
    }
    
    buffer.updatePixels();
  }
  
  function drawClickBursts(buffer) {
    for (var i = clickBurst.length - 1; i >= 0; i--) {
      var burst = clickBurst[i];
      burst.radius += 8;
      burst.strength *= 0.92;
      burst.alpha -= 0.015;
      
      if (burst.alpha <= 0) {
        clickBurst.splice(i, 1);
        continue;
      }
      
      buffer.push();
      buffer.noFill();
      buffer.strokeWeight(2);
      buffer.stroke(255, 255, 255, burst.alpha);
      buffer.ellipse(burst.x, burst.y, burst.radius * 2, burst.radius * 2);
      
      for (var r = 0; r < 6; r++) {
        var angle = r * p.TWO_PI / 6 + time * 2;
        var lineX = burst.x + Math.cos(angle) * burst.radius;
        var lineY = burst.y + Math.sin(angle) * burst.radius;
        buffer.line(
          burst.x, burst.y,
          lineX, lineY
        );
      }
      buffer.pop();
    }
  }
  
  function drawVignette(buffer) {
    var gradient = buffer.drawingContext.createRadialGradient(
      p.width / 2, p.height / 2, p.width * 0.2,
      p.width / 2, p.height / 2, p.width * 0.8
    );
    gradient.addColorStop(0, 'rgba(0, 0, 0, 0)');
    gradient.addColorStop(1, 'rgba(0, 0, 0, 0.7)');
    
    buffer.drawingContext.fillStyle = gradient;
    buffer.drawingContext.fillRect(0, 0, p.width, p.height);
  }
  
  p.draw = function() {
    time += 0.008;
    mouseInfluence = p.lerp(mouseInfluence, p.map(p.mouseX, 0, p.width, 0.2, -0.2), 0.05);
    
    if (p.frameCount % 30 === 0) {
      renderFogAtmosphere();
    }
    
    geoBuffer.clear();
    drawSacredGeometry(geoBuffer, p.width / 2, p.height / 2, Math.min(p.width, p.height) * 0.35);
    
    var projectedPoints = [];
    for (var i = 0; i < kleinPoints.length; i++) {
      applyFluidDynamics(kleinPoints[i], i);
      var proj = project4Dto2D(
        kleinPoints[i].x4,
        kleinPoints[i].y4,
        kleinPoints[i].z4,
        kleinPoints[i].w4,
        projectionMode
      );
      projectedPoints.push(proj);
    }
    
    drawKleinSurface(geoBuffer, projectedPoints);
    drawFlowingCurves(geoBuffer, projectedPoints);
    
    renderGlowLayer(projectedPoints);
    renderFluidSimulation(fluidBuffer);
    
    p.image(fogBuffer, 0, 0);
    
    fluidBuffer.drawingContext.globalCompositeOperation = 'SCREEN';
    p.image(fluidBuffer, 0, 0);
    fluidBuffer.drawingContext.globalCompositeOperation = 'SOURCE-OVER';
    
    geoBuffer.drawingContext.globalCompositeOperation = 'ADD';
    p.image(geoBuffer, 0, 0);
    geoBuffer.drawingContext.globalCompositeOperation = 'SOURCE-OVER';
    
    glowBuffer.drawingContext.globalCompositeOperation = 'SCREEN';
    p.image(glowBuffer, 0, 0);
    glowBuffer.drawingContext.globalCompositeOperation = 'SOURCE-OVER';
    
    drawClickBursts(glowBuffer);
    drawVignette(p);
  };
  
  p.mousePressed = function() {
    clickBurst.push({
      x: p.mouseX,
      y: p.mouseY,
      radius: 10,
      strength: 40,
      alpha: 0.8
    });
    
    if (clickBurst.length > 5) {
      clickBurst.shift();
    }
  };
  
  p.mouseMoved = function() {
    projectionMode = p.floor(p.map(p.mouseY, 0, p.height, 0, 3));
    projectionMode = p.constrain(projectionMode, 0, 2);
  };
  
  p.keyPressed = function() {
    if (p.key === 'g' || p.key === 'G') {
      showSacredGrid = !showSacredGrid;
    }
    if (p.key === 'r' || p.key === 'R') {
      initializeKleinBottle();
      initializeFlowField();
    }
    if (p.key === ' ') {
      time = 0;
      clickBurst = [];
    }
  };
  
  p.windowResized = function() {
    container = document.getElementById('p5-wrapper');
    p.resizeCanvas(container.offsetWidth, container.offsetHeight);
    
    fogBuffer.resizeCanvas(p.width, p.height);
    geoBuffer.resizeCanvas(p.width, p.height);
    glowBuffer.resizeCanvas(p.width, p.height);
    fluidBuffer.resizeCanvas(p.width, p.height);
    
    initializeFlowField();
    renderFogAtmosphere();
  };
};
// p5 init stripped

✨ AI 艺术解读

This artwork visualizes the Klein bottle - a topological impossibility where inside becomes outside in four dimensions - as a living fluid entity. The recursive 4D projection reveals hidden dimensional folds where the surface appears to pierce through itself, a mathematical impossibility in 3D space rendered visible through projection. Sacred geometry patterns emerge from the fluid flow, suggesting cosmic order within apparent chaos. The luminous white lines against charcoal fog evoke esoteric manuscripts and ancient astronomical charts, positioning this mathematical construct as a meditation on the nature of dimension, perception, and the infinite folding of space-time.

📝 补充说明

  • For performance, pixel-level fog rendering uses step size of 3 pixels; smaller step sizes create denser atmosphere at higher computational cost
  • The Klein bottle equations use radius = 100 + 40*cos(v/2) and additional terms for the handle portion that passes through the surface itself
  • Click bursts use radial expansion with exponential decay (strength *= 0.92) for organic dissipation effect
  • Projection mode selection via mouse Y position creates three distinct viewing angles of the 4D structure rotating through different planes
  • The sacred geometry grid pattern uses modulo operations to detect near-grid-line positions, creating subtle luminous interference patterns