Source of: Interactive Flow Field Particles, by GPT-5.2
152 lines, 15 of them comments · 1,454 tokens, written in 18 seconds for $0.021
1 // Interactive Flow Field Particles
2 // p5.js (no HTML needed)
3
4 let particles = [];
5 let numParticles = 4500;
6 let fieldScale = 0.006;
7 let timeScale = 0.0008;
8 let curlStrength = 1.6;
9 let palettes;
10 let palette;
11 let bg;
12
13 function setup() {
14 createCanvas(windowWidth, windowHeight);
15 pixelDensity(1);
16
17 palettes = [
18 ['#0B1320', '#5BC0EB', '#FDE74C', '#9BC53D', '#E55934'],
19 ['#07020D', '#5DB7DE', '#F1E9DB', '#A39B8B', '#7161EF'],
20 ['#0B0A07', '#F72585', '#B5179E', '#7209B7', '#3A0CA3', '#4361EE', '#4CC9F0'],
21 ['#081C15', '#1B4332', '#2D6A4F', '#52B788', '#95D5B2', '#D8F3DC'],
22 ['#111827', '#F43F5E', '#FB7185', '#FBBF24', '#34D399', '#60A5FA']
23 ];
24
25 resetSketch(true);
26 }
27
28 function resetSketch(full = false) {
29 palette = random(palettes);
30 bg = color(palette[0]);
31
32 background(bg);
33
34 particles.length = 0;
35 for (let i = 0; i < numParticles; i++) {
36 particles.push(new FlowParticle());
37 }
38
39 if (full) {
40 // Add a soft vignette overlay by drawing a translucent rect once.
41 noStroke();
42 fill(0, 20);
43 rect(0, 0, width, height);
44 }
45 }
46
47 function draw() {
48 // Slight fade for trails
49 noStroke();
50 fill(red(bg), green(bg), blue(bg), 12);
51 rect(0, 0, width, height);
52
53 // Update and draw particles
54 for (let i = 0; i < particles.length; i++) {
55 particles[i].step();
56 }
57
58 // Small UI hint
59 if (frameCount < 240) {
60 push();
61 noStroke();
62 fill(255, 140);
63 textSize(12);
64 textAlign(LEFT, BOTTOM);
65 text('Move mouse to stir • Click to reset', 12, height - 12);
66 pop();
67 }
68 }
69
70 function windowResized() {
71 resizeCanvas(windowWidth, windowHeight);
72 background(bg);
73 // Re-seed particles into new bounds
74 for (let p of particles) p.respawn();
75 }
76
77 function mousePressed() {
78 resetSketch(true);
79 }
80
81 class FlowParticle {
82 constructor() {
83 this.respawn();
84 this.hueColor = color(random(palette.slice(1)));
85 this.w = random(0.6, 1.4);
86 this.maxSpeed = random(1.2, 2.6);
87 this.life = int(random(120, 520));
88 }
89
90 respawn() {
91 this.pos = createVector(random(width), random(height));
92 this.prev = this.pos.copy();
93 this.vel = createVector(0, 0);
94 this.life = int(random(120, 520));
95 }
96
97 step() {
98 this.prev.set(this.pos);
99
100 // Flow field direction from noise
101 const t = millis() * timeScale;
102 const nx = this.pos.x * fieldScale;
103 const ny = this.pos.y * fieldScale;
104
105 // Mouse influence (a gentle vortex around the mouse)
106 const mdx = this.pos.x - mouseX;
107 const mdy = this.pos.y - mouseY;
108 const mDist = sqrt(mdx * mdx + mdy * mdy) + 0.0001;
109 const mouseFactor = constrain(180 / mDist, 0, 2.0);
110
111 // Base angle from noise
112 const n = noise(nx, ny, t);
113 let ang = n * TAU * 2.0;
114
115 // Add a subtle curl component by sampling nearby noise
116 const eps = 0.002;
117 const n1 = noise(nx + eps, ny, t);
118 const n2 = noise(nx, ny + eps, t);
119 const curl = (n1 - n2) * 12.0;
120 ang += curl * curlStrength;
121
122 // Mouse swirl
123 const swirl = atan2(mdy, mdx) + HALF_PI; // tangent
124 ang = lerp(ang, swirl, mouseFactor * 0.35);
125
126 const acc = p5.Vector.fromAngle(ang);
127 acc.mult(0.55);
128
129 this.vel.add(acc);
130 this.vel.limit(this.maxSpeed);
131 this.pos.add(this.vel);
132
133 // Draw trail segment
134 stroke(red(this.hueColor), green(this.hueColor), blue(this.hueColor), 90);
135 strokeWeight(this.w);
136 line(this.prev.x, this.prev.y, this.pos.x, this.pos.y);
137
138 // Wrap or respawn
139 this.life--;
140 if (
141 this.life <= 0 ||
142 this.pos.x < -10 || this.pos.x > width + 10 ||
143 this.pos.y < -10 || this.pos.y > height + 10
144 ) {
145 this.respawn();
146 this.hueColor = color(random(palette.slice(1)));
147 this.w = random(0.6, 1.4);
148 this.maxSpeed = random(1.2, 2.6);
149 }
150 }
151 }
152