118 lines, 2 of them comments ยท 1,622 tokens, written in 17 seconds for $0.084


  1  let particles = [];
  2  let flowField = [];
  3  let cols, rows;
  4  const scl = 20;
  5  let zoff = 0;
  6  let hueBase;
  7  
  8  function setup() {
  9    createCanvas(windowWidth, windowHeight);
 10    colorMode(HSB, 360, 100, 100, 100);
 11    cols = floor(width / scl) + 1;
 12    rows = floor(height / scl) + 1;
 13    initSketch();
 14  }
 15  
 16  function initSketch() {
 17    noiseSeed(floor(random(100000)));
 18    hueBase = random(360);
 19    background(0, 0, 5);
 20    particles = [];
 21    for (let i = 0; i < 1200; i++) {
 22      particles.push(new Particle());
 23    }
 24  }
 25  
 26  function draw() {
 27    // very subtle fade for long trails
 28    noStroke();
 29    fill(0, 0, 5, 2);
 30    rect(0, 0, width, height);
 31  
 32    // update flow field
 33    let yoff = 0;
 34    for (let y = 0; y < rows; y++) {
 35      let xoff = 0;
 36      for (let x = 0; x < cols; x++) {
 37        const angle = noise(xoff, yoff, zoff) * TWO_PI * 4;
 38        const v = p5.Vector.fromAngle(angle);
 39        v.setMag(0.4);
 40        flowField[x + y * cols] = v;
 41        xoff += 0.08;
 42      }
 43      yoff += 0.08;
 44    }
 45    zoff += 0.0025;
 46  
 47    for (const p of particles) {
 48      p.follow(flowField);
 49      p.update();
 50      p.edges();
 51      p.show();
 52    }
 53  }
 54  
 55  class Particle {
 56    constructor() {
 57      this.pos = createVector(random(width), random(height));
 58      this.prev = this.pos.copy();
 59      this.vel = createVector(0, 0);
 60      this.acc = createVector(0, 0);
 61      this.maxSpeed = random(1.5, 3.5);
 62      this.hueOffset = random(-40, 40);
 63      this.life = random(100, 400);
 64    }
 65  
 66    follow(field) {
 67      const x = constrain(floor(this.pos.x / scl), 0, cols - 1);
 68      const y = constrain(floor(this.pos.y / scl), 0, rows - 1);
 69      const force = field[x + y * cols];
 70      if (force) this.acc.add(force);
 71    }
 72  
 73    update() {
 74      this.vel.add(this.acc);
 75      this.vel.limit(this.maxSpeed);
 76      this.prev = this.pos.copy();
 77      this.pos.add(this.vel);
 78      this.acc.mult(0);
 79      this.life--;
 80      if (this.life <= 0) this.respawn();
 81    }
 82  
 83    respawn() {
 84      this.pos = createVector(random(width), random(height));
 85      this.prev = this.pos.copy();
 86      this.vel.mult(0);
 87      this.life = random(100, 400);
 88    }
 89  
 90    edges() {
 91      let wrapped = false;
 92      if (this.pos.x > width) { this.pos.x = 0; wrapped = true; }
 93      if (this.pos.x < 0) { this.pos.x = width; wrapped = true; }
 94      if (this.pos.y > height) { this.pos.y = 0; wrapped = true; }
 95      if (this.pos.y < 0) { this.pos.y = height; wrapped = true; }
 96      if (wrapped) this.prev = this.pos.copy();
 97    }
 98  
 99    show() {
100      const speed = this.vel.mag();
101      const h = (hueBase + this.hueOffset + speed * 25) % 360;
102      const b = map(speed, 0, this.maxSpeed, 50, 100);
103      stroke((h + 360) % 360, 70, b, 12);
104      strokeWeight(1);
105      line(this.prev.x, this.prev.y, this.pos.x, this.pos.y);
106    }
107  }
108  
109  function mousePressed() {
110    initSketch();
111  }
112  
113  function windowResized() {
114    resizeCanvas(windowWidth, windowHeight);
115    cols = floor(width / scl) + 1;
116    rows = floor(height / scl) + 1;
117    initSketch();
118  }