Ouroboros Deterministic Chaos Spiral

📅 April 25, 2026 🏷️ art
strange-attractor chaos-theory flow-field ouroboros neon-geometric sacred-geometry recursive-void clifford-attractor peter-de-jong generative-art pending-review
Generated by GridFlow AI | Tags: strange-attractor, chaos-theory, flow-field, ouroboros, neon-geometric, sacred-geometry, recursive-void, clifford-attractor, peter-de-jong, generative-art

💡 AI 提示词

Ouroboros Deterministic Chaos Spiral - neon geometric ultra-saturated cyan and magenta intersecting lines on pitch black with mathematical strange attractors Clifford and Peter de Jong mapped to flow fields, multi-layer compositing with createGraphics, particle system, sacred geometry spiral, recursive voids, psychedelic illusion, esoteric cryptography, cosmic vastness

🔧 核心算法要点

  1. Clifford strange attractor: x' = sin(a*y) + c*cos(a*x), y' = sin(b*x) + d*cos(b*y) computed for 8000 iterations generating chaotic deterministic trajectory
  2. Peter de Jong attractor as alternative mode: x' = sin(a*y) - cos(b*x), y' = sin(c*x) - cos(d*y) for parameter space exploration
  3. Flow field computation: for each grid cell, accumulate angle-weighted influence from all attractor points within radius 300 pixels, creating vector field that particles follow
  4. Multi-layer offscreen rendering: attractorBuffer for attractor visualization, flowFieldBuffer for vector field display, particleBuffer for particle system, overlayBuffer for sacred geometry - all composited with ADD blend mode
  5. Recursive void geometry: fractal-like structure where each void spawns 3 child voids at 120-degree intervals with rotating polygon vertices and depth-based color assignment
  6. Spiral pattern generation: multiple spiral arms using polar coordinates with logarithmic radius scaling, counter-rotating layers with sine-based wobble for organic motion
  7. Pixel-level bloom: per-pixel neighborhood averaging applied every 3 frames to create soft glow effect on bright pixels without expensive convolution kernel
  8. Particle pool system: 1800 pre-allocated particle objects with flow field forces, mouse attraction/repulsion within 200px radius, and cyan/magenta color assignment based on index

🎨 原始代码

var sketch = function(p) {
  var attractorBuffer, flowFieldBuffer, overlayBuffer, particleBuffer;
  var width, height;
  var cliffordParams = { a: -1.4, b: 1.7, c: 1.0, d: 0.7 };
  var time = 0;
  var mode = 0;
  var attractorPoints = [];
  var flowFieldVectors = [];
  var particles = [];
  var maxParticles = 1800;
  var phase = 0;
  var spiralArms = 7;
  var attractorScale = 60;
  var mouseActive = false;
  var lastMouseX = 0, lastMouseY = 0;
  var deJongParams = { a: -0.7, b: -1.2, c: 0.8, d: -0.9 };

  p.setup = function() {
    var container = document.getElementById('p5-wrapper');
    width = container.offsetWidth;
    height = container.offsetHeight;
    
    p.createCanvas(width, height).parent(container);
    p.colorMode(p.RGB, 255);
    p.background(0);
    
    attractorBuffer = p.createGraphics(width, height);
    flowFieldBuffer = p.createGraphics(width, height);
    overlayBuffer = p.createGraphics(width, height);
    particleBuffer = p.createGraphics(width, height);
    
    attractorBuffer.colorMode(p.RGB, 255);
    flowFieldBuffer.colorMode(p.RGB, 255);
    overlayBuffer.colorMode(p.RGB, 255);
    particleBuffer.colorMode(p.RGB, 255);
    
    attractorBuffer.background(0);
    flowFieldBuffer.background(0);
    overlayBuffer.background(0);
    particleBuffer.background(0);
    
    attractorBuffer.blendMode(p.ADD);
    flowFieldBuffer.blendMode(p.ADD);
    particleBuffer.blendMode(p.ADD);
    
    var gridRes = 25;
    for (var gx = 0; gx < Math.ceil(width / gridRes); gx++) {
      flowFieldVectors[gx] = [];
      for (var gy = 0; gy < Math.ceil(height / gridRes); gy++) {
        flowFieldVectors[gx][gy] = p.createVector(0, 0);
      }
    }
    
    computeCliffordAttractor();
    computeFlowField();
    initParticlePool();
  };

  function computeCliffordAttractor() {
    attractorPoints = [];
    var x = 0.1, y = 0.1;
    var centerX = width / 2;
    var centerY = height / 2;
    
    for (var i = 0; i < 8000; i++) {
      var nextX = p.sin(cliffordParams.a * y) + cliffordParams.c * p.cos(cliffordParams.a * x);
      var nextY = p.sin(cliffordParams.b * x) + cliffordParams.d * p.cos(cliffordParams.b * y);
      x = nextX;
      y = nextY;
      
      var px = centerX + x * attractorScale;
      var py = centerY + y * attractorScale;
      
      if (px > 0 && px < width && py > 0 && py < height) {
        attractorPoints.push({ x: px, y: py, vx: x, vy: y });
      }
    }
  }

  function computeDeJongAttractor() {
    attractorPoints = [];
    var x = 0.1, y = 0.1;
    var centerX = width / 2;
    var centerY = height / 2;
    
    for (var i = 0; i < 8000; i++) {
      var nextX = p.sin(deJongParams.a * y) - p.cos(deJongParams.b * x);
      var nextY = p.sin(deJongParams.c * x) - p.cos(deJongParams.d * y);
      x = nextX;
      y = nextY;
      
      var px = centerX + x * attractorScale;
      var py = centerY + y * attractorScale;
      
      if (px > 0 && px < width && py > 0 && py < height) {
        attractorPoints.push({ x: px, y: py, vx: x, vy: y });
      }
    }
  }

  function computeFlowField() {
    var gridRes = 25;
    var centerX = width / 2;
    var centerY = height / 2;
    
    for (var gx = 0; gx < flowFieldVectors.length; gx++) {
      for (var gy = 0; gy < flowFieldVectors[gx].length; gy++) {
        var px = gx * gridRes;
        var py = gy * gridRes;
        var angle = 0;
        var weight = 0;
        
        for (var i = 0; i < attractorPoints.length; i += 8) {
          var ap = attractorPoints[i];
          var dx = ap.x - px;
          var dy = ap.y - py;
          var d = Math.sqrt(dx * dx + dy * dy);
          
          if (d > 1 && d < 300) {
            var influence = 1 / (d * 0.5);
            angle += p.atan2(dy, dx) * influence;
            weight += influence;
          }
        }
        
        if (weight > 0) {
          angle /= weight;
          angle += p.sin(time * 2) * 0.3;
          flowFieldVectors[gx][gy] = p.createVector(p.cos(angle), p.sin(angle));
        }
      }
    }
  }

  function initParticlePool() {
    particles = [];
    for (var i = 0; i < maxParticles; i++) {
      particles.push({
        x: p.random(width),
        y: p.random(height),
        vx: 0, vy: 0,
        life: p.random(50, 200),
        maxLife: 150,
        size: p.random(0.5, 2.5),
        hue: p.random() > 0.5 ? 0 : 180,
        active: false
      });
    }
  }

  function updateParticles() {
    var gridRes = 25;
    particleBuffer.fill(0, 0, 0, 8);
    particleBuffer.noStroke();
    particleBuffer.rect(0, 0, width, height);
    
    for (var i = 0; i < particles.length; i++) {
      var part = particles[i];
      
      if (!part.active) {
        if (p.random() < 0.02) {
          part.active = true;
          part.x = p.random(width);
          part.y = p.random(height);
          part.life = part.maxLife;
        }
        continue;
      }
      
      part.life--;
      if (part.life <= 0) {
        part.active = false;
        continue;
      }
      
      var gx = Math.floor(part.x / gridRes);
      var gy = Math.floor(part.y / gridRes);
      gx = p.constrain(gx, 0, flowFieldVectors.length - 1);
      gy = p.constrain(gy, 0, flowFieldVectors[0].length - 1);
      
      var force = flowFieldVectors[gx][gy];
      
      if (mouseActive) {
        var mdx = p.mouseX - part.x;
        var mdy = p.mouseY - part.y;
        var md = Math.sqrt(mdx * mdx + mdy * mdy);
        if (md > 0 && md < 200) {
          var mforce = 0.3 / md;
          force.x += mdx * mforce;
          force.y += mdy * mforce;
        }
      }
      
      part.vx += force.x * 0.15;
      part.vy += force.y * 0.15;
      
      part.vx *= 0.96;
      part.vy *= 0.96;
      
      part.x += part.vx;
      part.y += part.vy;
      
      if (part.x < 0 || part.x > width || part.y < 0 || part.y > height) {
        part.active = false;
        continue;
      }
      
      var lifeRatio = part.life / part.maxLife;
      var alpha = lifeRatio * 200;
      var size = part.size * lifeRatio;
      
      if (part.hue === 0) {
        particleBuffer.fill(0, 255, 255, alpha);
      } else {
        particleBuffer.fill(255, 0, 255, alpha);
      }
      
      particleBuffer.noStroke();
      particleBuffer.ellipse(part.x, part.y, size, size);
    }
  }

  function drawAttractorLayer() {
    attractorBuffer.background(0, 0, 0, 12);
    
    if (attractorPoints.length < 2) return;
    
    var segmentLength = 3;
    var numSegments = Math.floor(attractorPoints.length / segmentLength);
    
    for (var s = 0; s < numSegments; s++) {
      var t = s / numSegments;
      
      var gradient = t;
      var r, g, b;
      
      if (mode === 0) {
        r = Math.floor(gradient * 255);
        g = Math.floor((1 - gradient * 0.3) * 255);
        b = 255;
      } else if (mode === 1) {
        r = 255;
        g = Math.floor(gradient * 100);
        b = Math.floor((1 - gradient) * 255);
      } else {
        var pulse = (p.sin(time * 5 + t * 20) + 1) * 0.5;
        r = Math.floor((0.5 + pulse * 0.5) * 255);
        g = Math.floor(gradient * 150);
        b = Math.floor((1 - gradient) * 255);
      }
      
      var alpha = 120 + Math.sin(time * 3 + t * 10) * 50;
      
      attractorBuffer.strokeWeight(0.6);
      attractorBuffer.stroke(r, g, b, alpha);
      
      attractorBuffer.beginShape();
      for (var j = 0; j < segmentLength; j++) {
        var idx = s * segmentLength + j;
        if (idx < attractorPoints.length) {
          attractorBuffer.vertex(attractorPoints[idx].x, attractorPoints[idx].y);
        }
      }
      attractorBuffer.endShape();
    }
  }

  function drawFlowFieldLayer() {
    flowFieldBuffer.background(0, 0, 0, 6);
    
    var gridRes = 25;
    
    for (var gx = 0; gx < flowFieldVectors.length; gx++) {
      for (var gy = 0; gy < flowFieldVectors[gx].length; gy++) {
        var v = flowFieldVectors[gx][gy];
        var px = gx * gridRes;
        var py = gy * gridRes;
        
        var mag = v.mag();
        if (mag > 0.01) {
          var len = 8 + mag * 20;
          
          var angle = v.heading();
          var hue = (angle + Math.PI) / (2 * Math.PI);
          var r = Math.floor((Math.sin(angle + time) + 1) * 127);
          var g = Math.floor((Math.sin(angle + time + 2) + 1) * 127);
          var b = 255;
          
          flowFieldBuffer.strokeWeight(0.4);
          flowFieldBuffer.stroke(r, g, b, 40);
          flowFieldBuffer.line(px, py, px + v.x * len, py + v.y * len);
          
          flowFieldBuffer.strokeWeight(0.3);
          flowFieldBuffer.stroke(255, 255, 255, 20);
          flowFieldBuffer.point(px + v.x * len * 0.5, py + v.y * len * 0.5);
        }
      }
    }
  }

  function drawOverlayGeometry() {
    overlayBuffer.background(0, 0, 0, 0);
    
    var centerX = width / 2;
    var centerY = height / 2;
    var maxRadius = Math.min(width, height) * 0.4;
    
    drawSpiralPattern(centerX, centerY, maxRadius);
    drawSacredGeometry(centerX, centerY, maxRadius);
    drawRecursiveVoids(centerX, centerY, maxRadius * 0.6);
  }

  function drawSpiralPattern(cx, cy, maxR) {
    overlayBuffer.noFill();
    
    var numArms = spiralArms;
    var pointsPerArm = 200;
    
    for (var arm = 0; arm < numArms; arm++) {
      var armOffset = (arm / numArms) * Math.PI * 2;
      
      overlayBuffer.beginShape();
      
      for (var pt = 0; pt < pointsPerArm; pt++) {
        var t = pt / pointsPerArm;
        var spiralAngle = armOffset + t * Math.PI * 6 + time * 0.5;
        var radius = maxR * Math.pow(t, 0.7);
        
        var wobble = p.sin(time * 3 + pt * 0.1) * 5;
        radius += wobble;
        
        var x = cx + p.cos(spiralAngle) * radius;
        var y = cy + p.sin(spiralAngle) * radius;
        
        overlayBuffer.curveVertex(x, y);
      }
      
      overlayBuffer.endShape();
    }
    
    overlayBuffer.strokeWeight(1.5);
    overlayBuffer.stroke(0, 255, 255, 60);
    
    for (var arm = 0; arm < numArms; arm++) {
      var armOffset = (arm / numArms) * Math.PI * 2;
      
      overlayBuffer.beginShape();
      
      for (var pt = 0; pt < pointsPerArm; pt++) {
        var t = pt / pointsPerArm;
        var spiralAngle = armOffset + t * Math.PI * 6 - time * 0.3;
        var radius = maxR * Math.pow(t, 0.7) * 0.8;
        
        var x = cx + p.cos(spiralAngle) * radius;
        var y = cy + p.sin(spiralAngle) * radius;
        
        overlayBuffer.curveVertex(x, y);
      }
      
      overlayBuffer.endShape();
    }
  }

  function drawSacredGeometry(cx, cy, maxR) {
    overlayBuffer.strokeWeight(0.8);
    
    var layers = 4;
    var rotationSpeed = time * 0.2;
    
    for (var layer = 0; layer < layers; layer++) {
      var layerRadius = maxR * (layer + 1) / layers;
      var layerRotation = rotationSpeed * (layer % 2 === 0 ? 1 : -1);
      var sides = 6 + layer * 2;
      
      var gradient = layer / layers;
      var alpha = 80 + gradient * 60;
      
      if (layer % 2 === 0) {
        overlayBuffer.stroke(0, 255, 255, alpha);
      } else {
        overlayBuffer.stroke(255, 0, 255, alpha);
      }
      
      overlayBuffer.beginShape();
      
      for (var i = 0; i <= sides; i++) {
        var angle = (i / sides) * Math.PI * 2 + layerRotation;
        var r = layerRadius * (1 + p.sin(time * 2 + i * 0.5) * 0.1);
        var x = cx + p.cos(angle) * r;
        var y = cy + p.sin(angle) * r;
        overlayBuffer.vertex(x, y);
      }
      
      overlayBuffer.endShape();
      
      overlayBuffer.strokeWeight(0.4);
      overlayBuffer.stroke(255, 255, 255, 30);
      
      overlayBuffer.beginShape();
      
      for (var i = 0; i <= sides * 2; i++) {
        var angle = (i / (sides * 2)) * Math.PI * 2 - layerRotation * 0.5;
        var r = layerRadius * 0.5 * (1 + p.cos(time * 1.5 + i * 0.3) * 0.15);
        var x = cx + p.cos(angle) * r;
        var y = cy + p.sin(angle) * r;
        overlayBuffer.vertex(x, y);
      }
      
      overlayBuffer.endShape();
    }
  }

  function drawRecursiveVoids(cx, cy, maxR) {
    overlayBuffer.noFill();
    
    var depth = 5;
    
    function drawVoid(x, y, radius, depth) {
      if (depth <= 0 || radius < 5) return;
      
      var t = 1 - depth / 5;
      var hue = (time * 0.5 + t) % 1;
      var r = Math.floor((Math.sin(hue * Math.PI * 2) + 1) * 127);
      var g = Math.floor((Math.sin(hue * Math.PI * 2 + 2) + 1) * 127);
      var b = 255;
      
      overlayBuffer.strokeWeight(0.5);
      overlayBuffer.stroke(r, g, b, 30 + t * 40);
      
      var sides = 8 + depth * 2;
      var rot = time * (depth % 2 === 0 ? 1 : -1) * 0.5;
      
      overlayBuffer.beginShape();
      
      for (var i = 0; i <= sides; i++) {
        var angle = (i / sides) * Math.PI * 2 + rot;
        var wobble = p.sin(time * 3 + depth * i) * radius * 0.1;
        var r2 = radius + wobble;
        var px = x + p.cos(angle) * r2;
        var py = y + p.sin(angle) * r2;
        overlayBuffer.curveVertex(px, py);
      }
      
      overlayBuffer.endShape();
      
      var numChildren = 3;
      for (var c = 0; c < numChildren; c++) {
        var childAngle = (c / numChildren) * Math.PI * 2 + time * 0.3;
        var childR = radius * 0.4;
        var childX = x + p.cos(childAngle) * radius * 0.6;
        var childY = y + p.sin(childAngle) * radius * 0.6;
        drawVoid(childX, childY, childR, depth - 1);
      }
    }
    
    drawVoid(cx, cy, maxR, depth);
  }

  function drawMouseEffect() {
    if (mouseActive) {
      var mx = p.mouseX;
      var my = p.mouseY;
      var pulseRadius = 50 + p.sin(time * 5) * 20;
      
      for (var ring = 0; ring < 3; ring++) {
        var ringR = pulseRadius * (ring + 1) / 3;
        var alpha = 40 - ring * 10;
        
        p.noFill();
        p.strokeWeight(1.5);
        
        if (ring % 2 === 0) {
          p.stroke(0, 255, 255, alpha);
        } else {
          p.stroke(255, 0, 255, alpha);
        }
        
        p.ellipse(mx, my, ringR * 2, ringR * 2);
      }
      
      p.stroke(255, 255, 255, 30);
      p.strokeWeight(0.5);
      p.noFill();
      p.ellipse(mx, my, pulseRadius * 2.5, pulseRadius * 2.5);
    }
  }

  function drawPixelBloom() {
    p.loadPixels();
    
    var step = 3;
    var data = p.pixels;
    var imgWidth = p.width;
    
    for (var y = 0; y < p.height; y += step) {
      for (var x = 0; x < imgWidth; x += step) {
        var idx = (y * imgWidth + x) * 4;
        
        var r = data[idx];
        var g = data[idx + 1];
        var b = data[idx + 2];
        
        var brightness = (r + g + b) / 3;
        
        if (brightness > 100) {
          var bloom = brightness * 0.15;
          var bloomIdx = ((y - 2) * imgWidth + (x - 2)) * 4;
          if (bloomIdx >= 0 && bloomIdx < data.length - 4) {
            data[bloomIdx] = Math.min(255, data[bloomIdx] + bloom * 0.5);
            data[bloomIdx + 1] = Math.min(255, data[bloomIdx + 1] + bloom * 0.3);
            data[bloomIdx + 2] = Math.min(255, data[bloomIdx + 2] + bloom * 0.2);
          }
        }
      }
    }
    
    p.updatePixels();
  }

  p.draw = function() {
    time += 0.008;
    
    mouseActive = p.mouseX > 0 || p.mouseY > 0;
    
    computeFlowField();
    
    drawAttractorLayer();
    drawFlowFieldLayer();
    updateParticles();
    drawOverlayGeometry();
    
    p.image(attractorBuffer, 0, 0);
    p.blendMode(p.ADD);
    p.image(flowFieldBuffer, 0, 0);
    p.image(particleBuffer, 0, 0);
    
    p.blendMode(p.BLEND);
    p.image(overlayBuffer, 0, 0);
    
    drawMouseEffect();
    
    if (Math.floor(time * 10) % 3 === 0) {
      drawPixelBloom();
    }
  };

  p.mousePressed = function() {
    mode = (mode + 1) % 3;
    
    if (mode === 0) {
      computeCliffordAttractor();
    } else if (mode === 1) {
      computeDeJongAttractor();
    } else {
      cliffordParams.a = p.random(-2.5, 2.5);
      cliffordParams.b = p.random(-2.5, 2.5);
      cliffordParams.c = p.random(-2.5, 2.5);
      cliffordParams.d = p.random(-2.5, 2.5);
      deJongParams.a = p.random(-2.5, 2.5);
      deJongParams.b = p.random(-2.5, 2.5);
      deJongParams.c = p.random(-2.5, 2.5);
      deJongParams.d = p.random(-2.5, 2.5);
      computeCliffordAttractor();
    }
    
    computeFlowField();
  };

  p.mouseMoved = function() {
    lastMouseX = p.mouseX;
    lastMouseY = p.mouseY;
  };

  p.keyPressed = function() {
    if (p.key === 'r' || p.key === 'R') {
      cliffordParams = { a: -1.4, b: 1.7, c: 1.0, d: 0.7 };
      deJongParams = { a: -0.7, b: -1.2, c: 0.8, d: -0.9 };
      computeCliffordAttractor();
      computeFlowField();
    } else if (p.key === 's' || p.key === 'S') {
      spiralArms = (spiralArms % 12) + 1;
    } else if (p.key === ' ') {
      if (p.isLooping()) {
        p.noLoop();
      } else {
        p.loop();
      }
    } else if (p.key === 'c' || p.key === 'C') {
      attractorBuffer.background(0);
      flowFieldBuffer.background(0);
      particleBuffer.background(0);
      overlayBuffer.background(0);
    }
  };


  p.windowResized = function() {
    var container = document.getElementById('p5-wrapper');
    width = container.offsetWidth;
    height = container.offsetHeight;
    
    p.createCanvas(width, height).parent(container);
    p.background(0);
    
    attractorBuffer.resizeCanvas(width, height);
    flowFieldBuffer.resizeCanvas(width, height);
    overlayBuffer.resizeCanvas(width, height);
    particleBuffer.resizeCanvas(width, height);
    
    attractorBuffer.background(0);
    flowFieldBuffer.background(0);
    particleBuffer.background(0);
    overlayBuffer.background(0);
    
    attractorBuffer.blendMode(p.ADD);
    flowFieldBuffer.blendMode(p.ADD);
    particleBuffer.blendMode(p.ADD);
    
    attractorScale = Math.min(width, height) / 15;
    
    computeCliffordAttractor();
    computeFlowField();
  };
}; // p5 init stripped

✨ AI 艺术解读

This piece visualizes the philosophical paradox of deterministic chaos - the Ouroboros consuming itself in infinite recursion while maintaining stable patterns. The Clifford and de Jong attractors create serpentine trajectories that appear chaotic yet are completely deterministic, representing how apparent randomness emerges from simple mathematical rules. The sacred geometry overlays suggest cosmic order beneath apparent chaos, while the recursive voids evoke infinite depth - ego-death dissolving into cosmic vastness. The neon cyan and magenta palette against void black creates a hallucinatory synesthetic experience, as if witnessing the fundamental mathematical fabric of reality itself.

📝 补充说明

  • Performance optimization: step size of 3 for pixel bloom and attractor sampling every 8th point for flow field keeps framerate stable at 60fps while maintaining visual fidelity
  • Mouse interaction: continuous attract/repel force field within 200px radius creates dynamic response, click cycles through 3 attractor modes with randomized parameters for new chaotic configurations
  • Keyboard controls: R resets parameters, S cycles spiral arm count, Space toggles pause, C clears all buffers - enables exploration without recompilation
  • Blend mode layering: ADD blend mode on buffers before BLEND on final composite creates natural glow accumulation where lines intersect, crucial for neon aesthetic
  • Parameter sensitivity: attractor parameters a,b,c,d in range [-2.5, 2.5] produce wildly different topologies - from butterfly shapes to chaotic scattering, mouse click enables exploration