Source of: Interactive Flow Field, by o3
110 lines, 16 of them comments ยท 2,174 tokens, written in 11 seconds for $0.018
1 let inc = 0.1; // noise increment
2 let scl = 20; // size of each grid cell
3 let cols, rows;
4 let zoff = 0; // noise z-offset (time)
5 let particles = []; // array of moving dots
6 let flowfield; // array of vectors
7
8 function setup() {
9 createCanvas(windowWidth, windowHeight);
10 colorMode(HSB, 255);
11 background(255);
12 cols = floor(width / scl);
13 rows = floor(height / scl);
14 flowfield = new Array(cols * rows);
15 // seed the canvas with particles
16 for (let i = 0; i < 4000; i++) particles.push(new Particle());
17 strokeWeight(1);
18 }
19
20 function draw() {
21 // ----- build the flow field -----
22 let yoff = 0;
23 for (let y = 0; y < rows; y++) {
24 let xoff = 0;
25 for (let x = 0; x < cols; x++) {
26 const idx = x + y * cols;
27 const angle = noise(xoff, yoff, zoff) * TWO_PI * 4; // more curls
28 const v = p5.Vector.fromAngle(angle).setMag(1);
29 flowfield[idx] = v;
30 xoff += inc;
31 }
32 yoff += inc;
33 }
34 zoff += 0.003; // animate field
35
36 // ----- move and draw particles -----
37 for (const p of particles) {
38 p.follow(flowfield);
39 p.update();
40 p.edges();
41 p.show();
42 }
43 }
44
45 function mousePressed() {
46 // burst of new particles at mouse position
47 for (let i = 0; i < 250; i++) particles.push(new Particle(mouseX, mouseY));
48 }
49
50 function keyPressed() {
51 if (key === ' ') background(255); // clear trails
52 }
53
54 function windowResized() {
55 resizeCanvas(windowWidth, windowHeight);
56 cols = floor(width / scl);
57 rows = floor(height / scl);
58 flowfield = new Array(cols * rows);
59 }
60
61 //--------------------------------------------------
62 // Particle class
63 //--------------------------------------------------
64 class Particle {
65 constructor(x = random(width), y = random(height)) {
66 this.pos = createVector(x, y);
67 this.prev = this.pos.copy();
68 this.vel = createVector();
69 this.acc = createVector();
70 this.maxspeed = 2;
71 this.hue = random(255);
72 }
73
74 follow(vectors) {
75 const x = floor(this.pos.x / scl);
76 const y = floor(this.pos.y / scl);
77 const idx = x + y * cols;
78 const force = vectors[idx];
79 if (force) this.applyForce(force);
80 }
81
82 applyForce(f) {
83 this.acc.add(f);
84 }
85
86 update() {
87 this.vel.add(this.acc);
88 this.vel.limit(this.maxspeed);
89 this.pos.add(this.vel);
90 this.acc.mult(0);
91 }
92
93 show() {
94 stroke(this.hue, 200, 255, 25);
95 line(this.pos.x, this.pos.y, this.prev.x, this.prev.y);
96 this.updatePrev();
97 this.hue = (this.hue + 0.2) % 255; // slow color cycle
98 }
99
100 updatePrev() {
101 this.prev.set(this.pos);
102 }
103
104 edges() {
105 if (this.pos.x > width) { this.pos.x = 0; this.updatePrev(); }
106 if (this.pos.x < 0) { this.pos.x = width; this.updatePrev(); }
107 if (this.pos.y > height){ this.pos.y = 0; this.updatePrev(); }
108 if (this.pos.y < 0) { this.pos.y = height; this.updatePrev(); }
109 }
110 }