158 lines, 7 of them comments ยท 1,484 tokens, written in 18 seconds for $0.021


  1  let particles = [];
  2  let nParticles = 2200;
  3  let zOff = 0;
  4  let seed;
  5  
  6  function setup() {
  7    createCanvas(windowWidth, windowHeight);
  8    pixelDensity(1);
  9    resetSketch();
 10  }
 11  
 12  function resetSketch() {
 13    seed = floor(random(1e9));
 14    randomSeed(seed);
 15    noiseSeed(seed);
 16  
 17    background(10);
 18    particles = [];
 19  
 20    for (let i = 0; i < nParticles; i++) {
 21      particles.push(new Particle(random(width), random(height)));
 22    }
 23  }
 24  
 25  function draw() {
 26    // subtle fade for trails
 27    noStroke();
 28    fill(10, 12);
 29    rect(0, 0, width, height);
 30  
 31    zOff += 0.002;
 32  
 33    // flow intensity responds to mouse
 34    const mx = (mouseX / max(1, width));
 35    const my = (mouseY / max(1, height));
 36    const flowScale = lerp(0.0015, 0.008, constrain(mx, 0, 1));
 37    const forceMag = lerp(0.15, 0.9, constrain(my, 0, 1));
 38  
 39    // draw particles
 40    blendMode(ADD);
 41    for (let p of particles) {
 42      const a = flowAngle(p.pos.x, p.pos.y, flowScale);
 43      const v = p5.Vector.fromAngle(a).mult(forceMag);
 44      p.applyForce(v);
 45      p.update();
 46      p.show(a);
 47      p.wrap();
 48    }
 49    blendMode(BLEND);
 50  
 51    // occasional new particles for variation
 52    if (frameCount % 30 === 0 && particles.length < nParticles + 400) {
 53      for (let i = 0; i < 30; i++) {
 54        particles.push(new Particle(random(width), random(height)));
 55      }
 56    }
 57  }
 58  
 59  function flowAngle(x, y, s) {
 60    // multi-octave noise for richer curls
 61    const nx = x * s;
 62    const ny = y * s;
 63    const n1 = noise(nx, ny, zOff);
 64    const n2 = noise(nx * 2.3 + 100, ny * 2.3 - 40, zOff * 1.2);
 65    const n3 = noise(nx * 5.1 - 20, ny * 5.1 + 80, zOff * 1.8);
 66  
 67    const n = (n1 * 0.55 + n2 * 0.30 + n3 * 0.15);
 68    return n * TWO_PI * 3.0;
 69  }
 70  
 71  class Particle {
 72    constructor(x, y) {
 73      this.pos = createVector(x, y);
 74      this.prev = this.pos.copy();
 75      this.vel = p5.Vector.random2D().mult(0.2);
 76      this.acc = createVector(0, 0);
 77  
 78      this.hue = random(0, 360);
 79      this.w = random(0.6, 1.6);
 80      this.maxSpeed = random(1.2, 3.2);
 81      this.life = floor(random(240, 900));
 82    }
 83  
 84    applyForce(f) {
 85      this.acc.add(f);
 86    }
 87  
 88    update() {
 89      this.prev.set(this.pos);
 90  
 91      this.vel.add(this.acc);
 92      this.vel.limit(this.maxSpeed);
 93      this.pos.add(this.vel);
 94  
 95      this.acc.mult(0);
 96  
 97      this.life--;
 98      if (this.life <= 0) this.respawn();
 99    }
100  
101    respawn() {
102      this.pos.set(random(width), random(height));
103      this.prev.set(this.pos);
104      this.vel = p5.Vector.random2D().mult(0.2);
105      this.life = floor(random(240, 900));
106      this.hue = (this.hue + random(30, 90)) % 360;
107    }
108  
109    show(a) {
110      // color tied to direction + personal hue
111      colorMode(HSB, 360, 100, 100, 100);
112      const h = (this.hue + degrees(a) * 0.15) % 360;
113      const sat = 70;
114      const bri = 95;
115      const alpha = 18;
116  
117      stroke(h, sat, bri, alpha);
118      strokeWeight(this.w);
119      line(this.prev.x, this.prev.y, this.pos.x, this.pos.y);
120    }
121  
122    wrap() {
123      let wrapped = false;
124  
125      if (this.pos.x < 0) { this.pos.x = width; wrapped = true; }
126      else if (this.pos.x > width) { this.pos.x = 0; wrapped = true; }
127  
128      if (this.pos.y < 0) { this.pos.y = height; wrapped = true; }
129      else if (this.pos.y > height) { this.pos.y = 0; wrapped = true; }
130  
131      if (wrapped) this.prev.set(this.pos);
132    }
133  }
134  
135  function mousePressed() {
136    resetSketch();
137  }
138  
139  function keyPressed() {
140    if (key === 's' || key === 'S') {
141      saveCanvas(`flowfield_${seed}_${nf(frameCount, 6)}`, 'png');
142    }
143    if (key === 'c' || key === 'C') {
144      background(10);
145    }
146  }
147  
148  function windowResized() {
149    resizeCanvas(windowWidth, windowHeight);
150    background(10);
151    // keep particles but move any out of bounds
152    for (let p of particles) {
153      p.pos.x = constrain(p.pos.x, 0, width);
154      p.pos.y = constrain(p.pos.y, 0, height);
155      p.prev.set(p.pos);
156    }
157  }
158