195 lines, 19 of them comments ยท 5,531 tokens, written in 67 seconds for $0.055


  1  let particles = [];
  2  let g; // graphics buffer for trails
  3  let noiseScale, zOff = 0, zInc;
  4  let turnMult; // how twisty the field is
  5  let fade = 6; // trail fade per frame (0..100 in HSB alpha)
  6  let paused = false;
  7  let seed;
  8  let paletteMode = 0; // 0: Aurora, 1: Fire, 2: Ocean, 3: Neon Night
  9  let uiShowFrames = 360; // show HUD for first N frames
 10  
 11  function setup() {
 12    createCanvas(windowWidth, windowHeight);
 13    colorMode(HSB, 360, 100, 100, 100);
 14    g = createGraphics(width, height);
 15    g.colorMode(HSB, 360, 100, 100, 100);
 16    g.background(0, 0, 0, 100);
 17    reseed();
 18  }
 19  
 20  function reseed() {
 21    seed = floor(random(1e9));
 22    noiseSeed(seed);
 23    randomSeed(seed * 3 + 1337);
 24    setFieldParams();
 25    initParticles();
 26    if (g) g.background(0, 0, 0, 100);
 27  }
 28  
 29  function setFieldParams() {
 30    // Noise scale tuned for various screen sizes
 31    const base = 0.0015 + random(0.0005, 0.003);
 32    const scaleAdj = map(min(width, height), 300, 2000, 1.4, 0.8, true);
 33    noiseScale = base * scaleAdj;
 34    zInc = random(0.002, 0.007);
 35    turnMult = random(1.8, 4.8);
 36    fade = floor(random([4, 5, 6, 7, 8]));
 37  }
 38  
 39  function initParticles() {
 40    particles.length = 0;
 41    const target = constrain(int((width * height) / 1500), 300, 3000);
 42    for (let i = 0; i < target; i++) {
 43      particles.push(new Particle(random(width), random(height)));
 44    }
 45  }
 46  
 47  class Particle {
 48    constructor(x, y) {
 49      this.pos = createVector(x, y);
 50      this.prev = this.pos.copy();
 51      this.speed = random(0.4, 2.2);
 52      this.weight = random(0.5, 1.9);
 53      this.hueBase = random(360);
 54      this.lastN = 0; // store last noise value for color
 55    }
 56  
 57    update() {
 58      // Sample the 3D noise field
 59      const nx = this.pos.x * noiseScale;
 60      const ny = this.pos.y * noiseScale;
 61      const n = noise(nx, ny, zOff);
 62      this.lastN = n;
 63      const angle = n * TAU * turnMult;
 64      const v = p5.Vector.fromAngle(angle);
 65      const sp = this.speed + map(n, 0, 1, 0.05, 0.6);
 66      this.prev.set(this.pos);
 67      this.pos.add(v.mult(sp));
 68  
 69      // Wrap around the edges with a small margin
 70      const m = 2;
 71      let wrapped = false;
 72      if (this.pos.x < -m) { this.pos.x = width + m; wrapped = true; }
 73      else if (this.pos.x > width + m) { this.pos.x = -m; wrapped = true; }
 74      if (this.pos.y < -m) { this.pos.y = height + m; wrapped = true; }
 75      else if (this.pos.y > height + m) { this.pos.y = -m; wrapped = true; }
 76      if (wrapped) this.prev.set(this.pos);
 77    }
 78  
 79    draw(gfx) {
 80      const c = getHSBA(this, this.lastN);
 81      gfx.stroke(c.h, c.s, c.b, c.a);
 82      gfx.strokeWeight(this.weight);
 83      gfx.line(this.prev.x, this.prev.y, this.pos.x, this.pos.y);
 84    }
 85  }
 86  
 87  function getHSBA(p, n) {
 88    // n is 0..1 from noise; use it to modulate hue/brightness
 89    let h, s, b, a;
 90    switch (paletteMode % 4) {
 91      case 0: // Aurora
 92        h = (p.hueBase + n * 220 + frameCount * 0.08) % 360;
 93        s = 80 + 20 * n;
 94        b = 45 + 55 * n;
 95        a = 16 + 18 * (1 - n); // softer in bright zones
 96        break;
 97      case 1: // Fire
 98        h = 10 + 50 * n; // reds to ambers
 99        s = 75 + 20 * n;
100        b = 60 + 40 * n;
101        a = 18 + 10 * (1 - n);
102        break;
103      case 2: // Ocean
104        h = 170 + 60 * n; // teals to blues
105        s = 60 + 30 * n;
106        b = 55 + 45 * n;
107        a = 16 + 16 * (1 - n);
108        break;
109      default: // Neon Night (purples/cyans)
110        h = (260 + 120 * n + p.hueBase * 0.1) % 360;
111        s = 70 + 30 * n;
112        b = 50 + 50 * n;
113        a = 18 + 14 * (1 - n);
114        break;
115    }
116    return { h, s, b, a };
117  }
118  
119  function draw() {
120    if (!paused) {
121      zOff += zInc;
122      // Gentle fade on the trail buffer
123      g.noStroke();
124      g.fill(0, 0, 0, fade);
125      g.rect(0, 0, width, height);
126  
127      for (let i = 0; i < particles.length; i++) {
128        const p = particles[i];
129        p.update();
130        p.draw(g);
131      }
132    }
133  
134    image(g, 0, 0, width, height);
135    drawHUD();
136  }
137  
138  function drawHUD() {
139    if (frameCount > uiShowFrames) return;
140    const a = map(frameCount, 0, uiShowFrames, 90, 0, true);
141    push();
142    noStroke();
143    fill(0, 0, 0, a * 0.6);
144    rect(14, 14, 330, 80, 8);
145    fill(0, 0, 100, a);
146    textSize(12);
147    text(
148      'Aurora Flow Field\nClick/drag: spawn particles\nS: save image   R: reseed   C: palette   Space: pause',
149      24, 34
150    );
151    pop();
152  }
153  
154  function addParticles(n, x, y) {
155    for (let i = 0; i < n; i++) {
156      const px = x + randomGaussian() * 12;
157      const py = y + randomGaussian() * 12;
158      particles.push(new Particle(constrain(px, 0, width), constrain(py, 0, height)));
159    }
160    // Cap the total to avoid runaway counts
161    const cap = constrain(int((width * height) / 1000), 600, 5000);
162    if (particles.length > cap) {
163      particles.splice(0, particles.length - cap);
164    }
165  }
166  
167  function mousePressed() {
168    addParticles(80, mouseX, mouseY);
169  }
170  
171  function mouseDragged() {
172    addParticles(20, mouseX, mouseY);
173  }
174  
175  function keyPressed() {
176    if (key === ' ' ) {
177      paused = !paused;
178    } else if (key === 's' || key === 'S') {
179      saveCanvas('aurora-flow-' + seed, 'png');
180    } else if (key === 'r' || key === 'R') {
181      reseed();
182    } else if (key === 'c' || key === 'C') {
183      paletteMode = (paletteMode + 1) % 4;
184    }
185  }
186  
187  function windowResized() {
188    resizeCanvas(windowWidth, windowHeight);
189    const ng = createGraphics(width, height);
190    ng.colorMode(HSB, 360, 100, 100, 100);
191    ng.background(0, 0, 0, 100);
192    g = ng;
193    setFieldParams();
194    initParticles();
195  }