146 lines, no comments ยท 2,013 tokens, written in 13 seconds for $0.0077


  1  let particles = [];
  2  const NUM_PARTICLES = 1200;
  3  let noiseScale = 0.005;
  4  let noiseZ = 0;
  5  let ripples = [];
  6  let baseHue = 180;
  7  
  8  function setup() {
  9    createCanvas(windowWidth, windowHeight);
 10    colorMode(HSB, 360, 100, 100, 1);
 11    background(0);
 12    for (let i = 0; i < NUM_PARTICLES; i++) {
 13      particles.push(new Particle());
 14    }
 15  }
 16  
 17  function draw() {
 18    blendMode(BLEND);
 19    background(0, 0, 0, 0.08);
 20  
 21    blendMode(ADD);
 22    noiseZ += 0.002;
 23    baseHue = (baseHue + 0.1) % 360;
 24  
 25    for (let i = ripples.length - 1; i >= 0; i--) {
 26      ripples[i].update();
 27      ripples[i].display();
 28      if (ripples[i].isDead()) {
 29        ripples.splice(i, 1);
 30      }
 31    }
 32  
 33    for (let p of particles) {
 34      p.update();
 35      p.edges();
 36      p.display();
 37    }
 38  }
 39  
 40  class Particle {
 41    constructor() {
 42      this.init();
 43    }
 44  
 45    init() {
 46      this.pos = createVector(random(width), random(height));
 47      this.prevPos = this.pos.copy();
 48      this.vel = createVector(0, 0);
 49      this.acc = createVector(0, 0);
 50      this.maxSpeed = random(2, 4.5);
 51      this.hueOffset = random(-40, 40);
 52      this.life = random(100, 300);
 53      this.maxLife = this.life;
 54      this.weight = random(1.2, 2.5);
 55    }
 56  
 57    update() {
 58      let angle = noise(this.pos.x * noiseScale, this.pos.y * noiseScale, noiseZ) * TWO_PI * 4;
 59      let flowForce = p5.Vector.fromAngle(angle).mult(0.2);
 60      this.acc.add(flowForce);
 61  
 62      if (mouseIsPressed) {
 63        let mouse = createVector(mouseX, mouseY);
 64        let dir = p5.Vector.sub(this.pos, mouse);
 65        let d = dir.mag();
 66        if (d < 200 && d > 0) {
 67          dir.normalize();
 68          let force = (200 - d) / 200;
 69          this.acc.add(dir.mult(force * 1.5));
 70        }
 71      }
 72  
 73      for (let r of ripples) {
 74        let d = dist(this.pos.x, this.pos.y, r.x, r.y);
 75        let diff = abs(d - r.radius);
 76        if (diff < 30) {
 77          let push = p5.Vector.sub(this.pos, createVector(r.x, r.y)).normalize();
 78          let strength = map(diff, 0, 30, 2, 0) * (r.life / r.maxLife);
 79          this.acc.add(push.mult(strength));
 80        }
 81      }
 82  
 83      this.vel.add(this.acc);
 84      this.vel.limit(this.maxSpeed);
 85      this.prevPos = this.pos.copy();
 86      this.pos.add(this.vel);
 87      this.acc.mult(0);
 88      this.life--;
 89  
 90      if (this.life <= 0) {
 91        this.init();
 92      }
 93    }
 94  
 95    edges() {
 96      if (this.pos.x < 0 || this.pos.x > width || this.pos.y < 0 || this.pos.y > height) {
 97        this.init();
 98      }
 99    }
100  
101    display() {
102      let currentHue = (baseHue + this.hueOffset + 360) % 360;
103      let alpha = map(this.life, 0, this.maxLife, 0, 0.8);
104      stroke(currentHue, 80, 95, alpha);
105      strokeWeight(this.weight);
106      line(this.prevPos.x, this.prevPos.y, this.pos.x, this.pos.y);
107    }
108  }
109  
110  class Ripple {
111    constructor(x, y) {
112      this.x = x;
113      this.y = y;
114      this.radius = 10;
115      this.speed = 6;
116      this.life = 60;
117      this.maxLife = 60;
118      this.hue = (baseHue + 60) % 360;
119    }
120  
121    update() {
122      this.radius += this.speed;
123      this.life--;
124    }
125  
126    display() {
127      noFill();
128      let alpha = map(this.life, 0, this.maxLife, 0, 0.4);
129      stroke(this.hue, 70, 100, alpha);
130      strokeWeight(2);
131      circle(this.x, this.y, this.radius * 2);
132    }
133  
134    isDead() {
135      return this.life <= 0;
136    }
137  }
138  
139  function mousePressed() {
140    ripples.push(new Ripple(mouseX, mouseY));
141  }
142  
143  function windowResized() {
144    resizeCanvas(windowWidth, windowHeight);
145    background(0);
146  }