174 lines, 2 of them comments ยท 1,756 tokens, written in 18 seconds for $0.036


  1  let particles = [];
  2  let fieldScale = 0.0026;
  3  let timeOffset = 0;
  4  let paletteShift = 0;
  5  
  6  function setup() {
  7    createCanvas(windowWidth, windowHeight);
  8    pixelDensity(min(2, pixelDensity()));
  9    colorMode(HSB, 360, 100, 100, 100);
 10    background(230, 45, 5);
 11    strokeCap(ROUND);
 12    createParticles();
 13  }
 14  
 15  function createParticles() {
 16    particles = [];
 17    const count = constrain(floor((width * height) / 4200), 180, 700);
 18  
 19    for (let i = 0; i < count; i++) {
 20      particles.push(new Particle(random(width), random(height)));
 21    }
 22  }
 23  
 24  function draw() {
 25    // Translucent paint slowly erases old trails.
 26    blendMode(BLEND);
 27    noStroke();
 28    fill(230, 45, 5, 7);
 29    rect(0, 0, width, height);
 30  
 31    blendMode(ADD);
 32    timeOffset += 0.0014;
 33  
 34    for (const particle of particles) {
 35      particle.followFlow();
 36      particle.interact(mouseX, mouseY);
 37      particle.update();
 38      particle.wrap();
 39      particle.display();
 40    }
 41  
 42    blendMode(BLEND);
 43    drawPointerGlow();
 44  }
 45  
 46  function drawPointerGlow() {
 47    if (mouseX < 0 || mouseX > width || mouseY < 0 || mouseY > height) return;
 48  
 49    noFill();
 50    const hue = (paletteShift + frameCount * 0.4) % 360;
 51  
 52    for (let r = 36; r > 4; r -= 8) {
 53      stroke(hue, 65, 100, map(r, 4, 36, 20, 2));
 54      strokeWeight(1);
 55      circle(mouseX, mouseY, r);
 56    }
 57  }
 58  
 59  class Particle {
 60    constructor(x, y) {
 61      this.pos = createVector(x, y);
 62      this.prev = this.pos.copy();
 63      this.vel = p5.Vector.random2D().mult(random(0.2, 1.2));
 64      this.acc = createVector();
 65      this.speed = random(1.2, 3.5);
 66      this.weight = random(0.5, 1.8);
 67      this.seed = random(1000);
 68      this.life = random(1000);
 69    }
 70  
 71    followFlow() {
 72      const n = noise(
 73        this.pos.x * fieldScale,
 74        this.pos.y * fieldScale,
 75        timeOffset + this.seed * 0.001
 76      );
 77  
 78      const angle = n * TWO_PI * 3.2 + sin(timeOffset * 5) * 0.35;
 79      const force = p5.Vector.fromAngle(angle).mult(0.075);
 80      this.acc.add(force);
 81    }
 82  
 83    interact(x, y) {
 84      const pointer = createVector(x, y);
 85      const delta = p5.Vector.sub(pointer, this.pos);
 86      const distance = delta.mag();
 87      const radius = min(width, height) * 0.22;
 88  
 89      if (distance > 1 && distance < radius) {
 90        const strength = pow(1 - distance / radius, 2) * 0.38;
 91        delta.normalize();
 92  
 93        // Hold the pointer to attract; otherwise it gently pushes away.
 94        if (mouseIsPressed) {
 95          this.acc.add(delta.mult(strength));
 96        } else {
 97          this.acc.sub(delta.mult(strength * 0.45));
 98        }
 99      }
100    }
101  
102    update() {
103      this.prev.set(this.pos);
104      this.vel.add(this.acc);
105      this.vel.limit(this.speed);
106      this.pos.add(this.vel);
107      this.acc.mult(0);
108      this.life += 0.01;
109    }
110  
111    wrap() {
112      let wrapped = false;
113  
114      if (this.pos.x < -5) {
115        this.pos.x = width + 5;
116        wrapped = true;
117      } else if (this.pos.x > width + 5) {
118        this.pos.x = -5;
119        wrapped = true;
120      }
121  
122      if (this.pos.y < -5) {
123        this.pos.y = height + 5;
124        wrapped = true;
125      } else if (this.pos.y > height + 5) {
126        this.pos.y = -5;
127        wrapped = true;
128      }
129  
130      if (wrapped) this.prev.set(this.pos);
131    }
132  
133    display() {
134      const directionHue = map(this.vel.heading(), -PI, PI, 0, 180);
135      const hue = (paletteShift + directionHue + noise(this.life) * 90) % 360;
136      const alpha = map(this.vel.mag(), 0, this.speed, 12, 55);
137  
138      stroke(hue, 75, 100, alpha);
139      strokeWeight(this.weight);
140      line(this.prev.x, this.prev.y, this.pos.x, this.pos.y);
141  
142      if (this.weight > 1.45) {
143        stroke(hue, 30, 100, alpha * 0.35);
144        strokeWeight(this.weight * 3);
145        point(this.pos.x, this.pos.y);
146      }
147    }
148  }
149  
150  function mouseClicked() {
151    paletteShift = (paletteShift + random(55, 160)) % 360;
152    noiseSeed(floor(random(100000)));
153    background(230, 45, 5);
154    createParticles();
155  }
156  
157  function keyPressed() {
158    if (key === 'r' || key === 'R') {
159      paletteShift = random(360);
160      noiseSeed(floor(random(100000)));
161      background(230, 45, 5);
162      createParticles();
163    }
164  
165    if (key === 's' || key === 'S') {
166      saveCanvas('neon-current', 'png');
167    }
168  }
169  
170  function windowResized() {
171    resizeCanvas(windowWidth, windowHeight);
172    background(230, 45, 5);
173    createParticles();
174  }