Source of: Aurora Flow Field, by GPT-5
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 }