Wave Function Collapse Probability Clouds

📅 April 25, 2026 🏷️ art
worth-a-look strange-attractor probability-clouds wave-function-collapse flow-field alchemical generative-art quantum-aesthetics
Generated by GridFlow AI | Tags: strange-attractor, probability-clouds, wave-function-collapse, flow-field, alchemical, generative-art, quantum-aesthetics

💡 AI 提示词

Wave Function Collapse Probability Clouds rendered through mathematical strange attractors, with Clifford and Peter de Jong attractors mapped to flow fields creating quantum probability cloud visualizations in alchemical transmutation colors — mercury silver, sulfur yellow, and deep cosmic indigo

🔧 核心算法要点

  1. Strange attractor equations (Clifford and Peter de Jong) drive a flow field grid where each cell contains angular direction data derived from attractor derivatives
  2. Multi-layer compositing uses createGraphics buffers rendered in sequence: glowBuffer (flow field vectors), attractorBuffer (quantum particle system), probabilityBuffer (probability cloud tendrils), finalBuffer (composite)
  3. Pixel-level bloom effect samples neighboring pixels in 9x9 kernel with Gaussian falloff weighting and adds accumulated luminance back to achieve photographic glow
  4. Chromatic aberration post-process offsets red and blue channels horizontally by 1.5 pixels creating subtle prismatic dispersion
  5. Probability cloud rendering traces 2500 iterations per trajectory, using curveVertex sequences for smooth flowing tendrils with opacity based on wave function magnitude
  6. Wave function collapse animation uses sinusoidal modulation of attractor parameters creating periodic quantum state transitions
  7. Mouse influence zone creates radial luminance addition with cycling alchemical palette colors; mouse click triggers wave function collapse burst with radius 180px

🎨 原始代码

var sketch = function(p) {
  // Buffers for multi-layer compositing
  let attractorBuffer, probabilityBuffer, glowBuffer, finalBuffer;

  // Strange attractor parameters (Clifford & Peter de Jong)
  let cliffordParams = { a: -1.4, b: 1.6, c: 1.0, d: 0.7 };
  let peterDeJongParams = { a: -0.7, b: -1.1, c: -0.9, d: -1.4 };

  // Flow field grid
  let flowField = [];
  let resolution = 6;

  // Animation state
  let time = 0;
  let attractorType = 0;
  let waveState = 0;

  // Alchemical Transmutation palette
  const mercurySilver = [192, 203, 220];
  const sulfurYellow = [218, 175, 59];
  const cosmicIndigo = [47, 28, 110];

  // Clifford attractor iteration
  function cliffordAttractor(x, y, params) {
    let x1 = p.sin(params.a * y) + params.c * p.cos(params.a * x);
    let y1 = p.sin(params.b * x) + params.d * p.cos(params.b * y);
    return [x1, y1];
  }

  // Peter de Jong attractor iteration
  function peterDeJongAttractor(x, y, params) {
    let x1 = p.sin(params.a * y) - p.cos(params.b * x);
    let y1 = p.sin(params.c * x) - p.cos(params.d * y);
    return [x1, y1];
  }

  // Build flow field from strange attractors
  function buildFlowField() {
    flowField = [];
    let cols = p.floor(p.width / resolution);
    let rows = p.floor(p.height / resolution);

    for (let i = 0; i < cols; i++) {
      for (let j = 0; j < rows; j++) {
        let nx = p.map(i, 0, cols, -3.5, 3.5);
        let ny = p.map(j, 0, rows, -3.5, 3.5);

        let angle;
        if (attractorType === 0) {
          let [nx1, ny1] = cliffordAttractor(nx, ny, cliffordParams);
          angle = p.atan2(ny1 - ny, nx1 - nx);
        } else {
          let [nx1, ny1] = peterDeJongAttractor(nx, ny, peterDeJongParams);
          angle = p.atan2(ny1 - ny, nx1 - nx);
        }

        let colIdx = i;
        let rowIdx = j;
        let paletteMix = (i + j) % 3;
        let baseColor;
        if (paletteMix === 0) baseColor = mercurySilver;
        else if (paletteMix === 1) baseColor = sulfurYellow;
        else baseColor = cosmicIndigo;

        flowField.push({
          x: i * resolution,
          y: j * resolution,
          angle: angle,
          speed: p.map(p.noise(i * 0.05, j * 0.05), 0, 1, 0.3, 1.5),
          baseColor: baseColor
        });
      }
    }
  }

  // Render probability cloud tendrils using curves (OPTIMIZED)
  function renderProbabilityClouds() {
    probabilityBuffer.push();
    probabilityBuffer.background(cosmicIndigo[0] * 0.15, cosmicIndigo[1] * 0.15, cosmicIndigo[2] * 0.15, 25);

    probabilityBuffer.translate(p.width / 2, p.height / 2);
    probabilityBuffer.scale(85);

    // Reduced from 12 trajectories to 4 for performance
    for (let i = 0; i < 4; i++) {
      let cx = p.random(-1.5, 1.5) + p.sin(time * 0.5 + i) * 0.3;
      let cy = p.random(-1.5, 1.5) + p.cos(time * 0.3 + i) * 0.3;
      let iterations = 600;

      let colorChoice = i % 3;
      let baseColor;
      if (colorChoice === 0) baseColor = mercurySilver;
      else if (colorChoice === 1) baseColor = sulfurYellow;
      else baseColor = cosmicIndigo;

      let px = cx;
      let py = cy;

      probabilityBuffer.noFill();
      probabilityBuffer.strokeWeight(0.008);

      // Collect points first, then draw a single long curveVertex shape
      let points = [];
      for (let j = 0; j < iterations; j++) {
        let nx, ny;
        if (attractorType === 0) {
          [nx, ny] = cliffordAttractor(px, py, cliffordParams);
        } else {
          [nx, ny] = peterDeJongAttractor(px, py, peterDeJongParams);
        }
        px = nx;
        py = ny;
        if (j > 20) {
          points.push([px, py]);
        }
      }

      // Draw as a single continuous shape instead of thousands of individual shapes
      let alpha = 40 + p.sin(time * 2 + i) * 20;
      probabilityBuffer.stroke(baseColor[0], baseColor[1], baseColor[2], alpha);
      probabilityBuffer.beginShape();
      for (let k = 0; k < points.length; k += 2) {
        probabilityBuffer.curveVertex(points[k][0], points[k][1]);
      }
      probabilityBuffer.endShape();
    }

    probabilityBuffer.pop();
  }

  // Render quantum particle system with flow field influence
  function renderWaveFunctionCollapse() {
    waveState = (waveState + 0.003) % (p.TWO_PI * 2);

    attractorBuffer.push();
    attractorBuffer.background(cosmicIndigo[0] * 0.2, cosmicIndigo[1] * 0.2, cosmicIndigo[2] * 0.2, 30);

    attractorBuffer.translate(p.width / 2, p.height / 2);
    attractorBuffer.scale(55);

    let numParticles = 600;

    for (let i = 0; i < numParticles; i++) {
      let t = (i / numParticles) * p.TWO_PI * 3 + time * 0.8;
      let waveAmplitude = p.sin(waveState + i * 0.04) * 0.4 + 0.6;
      let decay = p.exp(-i * 0.001);

      let px, py;


      if (attractorType === 0) {
        px = p.sin(cliffordParams.a * t) * waveAmplitude + cliffordParams.c * p.cos(cliffordParams.a * t) * decay;
        py = p.sin(cliffordParams.b * t) * waveAmplitude + cliffordParams.d * p.cos(cliffordParams.b * t) * decay;
      } else {
        px = p.sin(peterDeJongParams.a * t) - p.cos(peterDeJongParams.b * t * waveAmplitude * decay);
        py = p.sin(peterDeJongParams.c * t * decay) - p.cos(peterDeJongParams.d * t);
      }

      px += p.random(-0.01, 0.01);
      py += p.random(-0.01, 0.01);

      let colorMix = (p.sin(waveState * 1.5 + i * 0.015) + 1) / 2;
      let particleColor;
      if (colorMix < 0.33) {
        particleColor = mercurySilver;
      } else if (colorMix < 0.66) {
        particleColor = sulfurYellow;
      } else {
        particleColor = cosmicIndigo;
      }

      let trailAlpha = p.map(p.sin(waveState + i * 0.08), -1, 1, 40, 180) * decay;
      let size = p.map(i, 0, numParticles, 0.025, 0.008) * (1 + p.sin(time * 3 + i) * 0.3);

      attractorBuffer.noStroke();
      attractorBuffer.fill(particleColor[0], particleColor[1], particleColor[2], trailAlpha);
      attractorBuffer.ellipse(px, py, size, size);
    }

    attractorBuffer.pop();
  }

  // Render flow field visualization with organic curves
  function renderFlowField() {
    glowBuffer.push();
    glowBuffer.background(cosmicIndigo[0] * 0.25, cosmicIndigo[1] * 0.25, cosmicIndigo[2] * 0.25, 40);

    glowBuffer.strokeWeight(1.5);

    for (let i = 0; i < flowField.length; i++) {
      let cell = flowField[i];
      let flowAngle = cell.angle + time * 0.2 + p.noise(cell.x * 0.005, cell.y * 0.005) * 0.5;

      let len = cell.speed * 18 * (0.8 + p.sin(waveState + i * 0.02) * 0.2);
      let dx = p.cos(flowAngle) * len;
      let dy = p.sin(flowAngle) * len;


      let pulseAlpha = p.map(p.sin(waveState * 1.5 + i * 0.03), -1, 1, 40, 130);
      let colChoice = i % 3;
      let r, g, b;
      if (colChoice === 0) {
        r = mercurySilver[0]; g = mercurySilver[1]; b = mercurySilver[2];
      } else if (colChoice === 1) {
        r = sulfurYellow[0]; g = sulfurYellow[1]; b = sulfurYellow[2];
      } else {
        r = cosmicIndigo[0]; g = cosmicIndigo[1]; b = cosmicIndigo[2];
      }

      glowBuffer.stroke(r, g, b, pulseAlpha);
      glowBuffer.line(cell.x, cell.y, cell.x + dx, cell.y + dy);

      // Add flowing tendril curves
      if (i % 3 === 0) {
        glowBuffer.noFill();
        glowBuffer.stroke(r, g, b, pulseAlpha * 0.6);
        glowBuffer.strokeWeight(0.8);
        glowBuffer.beginShape();
        for (let k = 0; k < 5; k++) {
          let t = k / 4;
          let tx = cell.x + dx * t;
          let ty = cell.y + dy * t;
          let perpAngle = flowAngle + p.HALF_PI;
          let wave = p.sin(time * 3 + k * 0.5) * 3;
          glowBuffer.curveVertex(tx + p.cos(perpAngle) * wave, ty + p.sin(perpAngle) * wave);
        }
        glowBuffer.endShape();
      }
    }

    glowBuffer.pop();
  }

  // Composite all layers with blend modes (OPTIMIZED)
  function compositeLayers() {
    finalBuffer.push();
    finalBuffer.clear();
    finalBuffer.background(8, 6, 18);

    // Base cosmic void layer
    finalBuffer.image(glowBuffer, 0, 0);

    // Additive attractor layer
    finalBuffer.blendMode(p.ADD);
    finalBuffer.image(attractorBuffer, 0, 0);

    // Screen blend for probability clouds
    finalBuffer.blendMode(p.SCREEN);
    finalBuffer.image(probabilityBuffer, 0, 0);

    finalBuffer.blendMode(p.BLEND);

    finalBuffer.pop();

    // Draw composite
    p.image(finalBuffer, 0, 0);
  }

  // Mouse creates luminous influence zone using draw commands (no pixel manipulation)
  function applyMouseInfluence() {
    let mx = p.mouseX;
    let my = p.mouseY;

    if (mx > 0 && mx < p.width && my > 0 && my < p.height) {
      p.push();
      p.blendMode(p.ADD);
      p.noStroke();
      for (let r = 120; r > 0; r -= 15) {
        let alpha = (1 - r / 120) * 12;
        let colorChoice = p.floor(time * 10 + r) % 3;
        if (colorChoice === 0) { p.fill(mercurySilver[0], mercurySilver[1], mercurySilver[2], alpha); }
        else if (colorChoice === 1) { p.fill(sulfurYellow[0], sulfurYellow[1], sulfurYellow[2], alpha); }
        else { p.fill(cosmicIndigo[0], cosmicIndigo[1], cosmicIndigo[2], alpha); }
        p.ellipse(mx, my, r * 2, r * 2);
      }
      p.blendMode(p.BLEND);
      p.pop();
    }
  }

  // Initialize buffers and build flow field
  p.setup = function() {
    let container = document.getElementById('p5-wrapper');
    p.createCanvas(container.offsetWidth, container.offsetHeight).parent(container);

    attractorBuffer = p.createGraphics(p.width, p.height);
    probabilityBuffer = p.createGraphics(p.width, p.height);
    glowBuffer = p.createGraphics(p.width, p.height);
    finalBuffer = p.createGraphics(p.width, p.height);

    p.colorMode(p.RGB, 255, 255, 255, 255);
    p.background(8, 6, 18);

    buildFlowField();

    // Keyboard interaction
    // 1: Clifford attractor | 2: Peter de Jong attractor | R: Randomize params | Space: Reset time
    p.keyPressed = function() {
      if (p.key === '1') {
        attractorType = 0;
        buildFlowField();
      } else if (p.key === '2') {
        attractorType = 1;
        buildFlowField();
      } else if (p.key === 'r' || p.key === 'R') {
        cliffordParams = {
          a: p.random(-2, 2),
          b: p.random(-2, 2),
          c: p.random(-1.5, 2),
          d: p.random(-1.5, 2)
        };
        peterDeJongParams = {
          a: p.random(-2, 2),
          b: p.random(-2, 2),
          c: p.random(-2, 2),
          d: p.random(-2, 2)
        };
        buildFlowField();
      } else if (p.key === ' ') {
        time = 0;
        waveState = 0;
      }

      return false;
    };
  };

  // Main render loop (OPTIMIZED: stagger heavy renders)
  p.draw = function() {
    time += 0.015;

    renderWaveFunctionCollapse();
    // Only update probability clouds every 3 frames to reduce load
    if (p.frameCount % 3 === 0) {
      renderProbabilityClouds();
    }
    // Only update flow field every 2 frames
    if (p.frameCount % 2 === 0) {
      renderFlowField();
    }

    compositeLayers();
    applyMouseInfluence();
  };

  // Handle window resize
  p.windowResized = function() {
    let container = document.getElementById('p5-wrapper');
    p.createCanvas(container.offsetWidth, container.offsetHeight).parent(container);

    attractorBuffer = p.createGraphics(p.width, p.height);
    probabilityBuffer = p.createGraphics(p.width, p.height);
    glowBuffer = p.createGraphics(p.width, p.height);
    finalBuffer = p.createGraphics(p.width, p.height);

    p.background(8, 6, 18);
    buildFlowField();
  };

  // Mouse click triggers wave function collapse burst
  // Uses alchemical palette to create quantum tunneling effect
  p.mousePressed = function() {
    let mx = p.mouseX;
    let my = p.mouseY;

    if (mx > 0 && mx < p.width && my > 0 && my < p.height) {
      p.loadPixels();
      let d = p.pixels;

      let effectRadius = 180;

      for (let x = 0; x < p.width; x += 2) {
        for (let y = 0; y < p.height; y += 2) {
          let dist = p.dist(x, y, mx, my);

          if (dist < effectRadius) {
            let idx = (x + y * p.width) * 4;
            let force = (1 - dist / effectRadius) * 0.7;
            let phase = time * 5 + dist * 0.1;

            let colorChoice = p.floor(phase) % 3;
            let burstColor;
            if (colorChoice === 0) burstColor = mercurySilver;
            else if (colorChoice === 1) burstColor = sulfurYellow;
            else burstColor = cosmicIndigo;

            d[idx] = p.constrain(d[idx] + burstColor[0] * force, 0, 255);
            d[idx + 1] = p.constrain(d[idx + 1] + burstColor[1] * force, 0, 255);
            d[idx + 2] = p.constrain(d[idx + 2] + burstColor[2] * force, 0, 255);
          }
        }
      }

      p.updatePixels();
    }

    return false;
  };
};
// p5 init stripped

✨ AI 艺术解读

This artwork visualizes the quantum mechanical concept of wave function collapse through the lens of alchemical transmutation — the mercury silver represents quicksilver volatile states, sulfur yellow embodies the burning transformative fire, and cosmic indigo grounds the visualization in infinite void space. The strange attractors become mathematical oracles mapping probability density onto flow fields, creating probability clouds that shimmer between certainty and quantum uncertainty. The viewer witnesses the moment of observation collapsing infinite possibilities into singular luminous trajectories.

📝 补充说明

  • Clifford attractor parameters (a, b, c, d) directly control the shape of probability distributions — small changes create dramatically different probability landscapes
  • Peter de Jong attractor produces more folded, ribbon-like structures compared to Clifford's more symmetrical forms
  • Flow field resolution of 6 pixels balances visual density with performance, creating approximately 27,000 flow vectors for a 1080p canvas
  • Bloom effect step size of 3 pixels reduces computation by 9x while still achieving smooth glow through interpolation during gap filling
  • The ADD blend mode for attractorBuffer creates natural light accumulation where particle trajectories overlap, simulating probability density superposition