Source of: Flow Field Particle Canvas, by DeepSeek V4.1 Flash 1017
104 lines, 9 of them comments ยท 1,959 tokens, written in 11 seconds for $0.0011
1 // Flow Field Particles
2 // Particles trace a Perlin noise vector field, leaving colored trails.
3 // Move the mouse to push particles away.
4
5 let particles = [];
6 const NUM_PARTICLES = 1400;
7 const NOISE_SCALE = 0.0035;
8 let t = 0;
9 let hueShift = 0;
10
11 function setup() {
12 createCanvas(windowWidth, windowHeight);
13 colorMode(HSB, 360, 100, 100, 1);
14 background(0, 0, 4);
15 for (let i = 0; i < NUM_PARTICLES; i++) {
16 particles.push(new Particle(random(width), random(height)));
17 }
18 }
19
20 function draw() {
21 // Fade the previous frame to create soft trails
22 noStroke();
23 fill(0, 0, 4, 0.035);
24 rect(0, 0, width, height);
25
26 hueShift = (hueShift + 0.25) % 360;
27
28 for (const p of particles) {
29 p.update();
30 p.display();
31 }
32
33 t += 0.004;
34 }
35
36 class Particle {
37 constructor(x, y) {
38 this.pos = createVector(x, y);
39 this.prev = this.pos.copy();
40 this.vel = p5.Vector.random2D().mult(random(1, 3));
41 this.hue = random(360);
42 this.wrapped = false;
43 }
44
45 update() {
46 this.prev.set(this.pos);
47
48 // Sample the flow field
49 const angle = noise(
50 this.pos.x * NOISE_SCALE,
51 this.pos.y * NOISE_SCALE,
52 t
53 ) * TWO_PI * 3;
54
55 const force = p5.Vector.fromAngle(angle).mult(0.55);
56 this.vel.add(force);
57 this.vel.limit(2.8);
58 this.pos.add(this.vel);
59
60 // Mouse repulsion
61 const d = dist(this.pos.x, this.pos.y, mouseX, mouseY);
62 if (d < 160 && d > 0.01) {
63 const push = p5.Vector.sub(this.pos, createVector(mouseX, mouseY));
64 push.setMag(map(d, 0, 160, 4, 0));
65 this.pos.add(push);
66 }
67
68 // Wrap edges (and flag so we don't draw across the screen)
69 this.wrapped = false;
70 if (this.pos.x < 0) { this.pos.x += width; this.wrapped = true; }
71 if (this.pos.x > width) { this.pos.x -= width; this.wrapped = true; }
72 if (this.pos.y < 0) { this.pos.y += height; this.wrapped = true; }
73 if (this.pos.y > height) { this.pos.y -= height; this.wrapped = true; }
74
75 this.hue = (this.hue + 0.5) % 360;
76 }
77
78 display() {
79 if (this.wrapped) return;
80
81 // Color shifts along the direction of travel for a silky palette
82 const h = (this.hue + hueShift + this.vel.heading() * 20) % 360;
83 stroke((h + 360) % 360, 70, 95, 0.55);
84 strokeWeight(1.2);
85 line(this.prev.x, this.prev.y, this.pos.x, this.pos.y);
86 }
87 }
88
89 function windowResized() {
90 resizeCanvas(windowWidth, windowHeight);
91 background(0, 0, 4);
92 }
93
94 function keyPressed() {
95 // Press 'r' to reseed the field and clear the canvas
96 if (key === 'r' || key === 'R') {
97 noiseSeed(floor(random(100000)));
98 background(0, 0, 4);
99 particles = [];
100 for (let i = 0; i < NUM_PARTICLES; i++) {
101 particles.push(new Particle(random(width), random(height)));
102 }
103 }
104 }