Source of: Bioluminescent Flow Field, by Gemini 4 Argon
112 lines, no comments ยท 1,189 tokens, written in 33 seconds for $0.012
1 let particles = [];
2 const numParticles = 1800;
3 const noiseScale = 0.004;
4 let zOff = 0;
5 let baseHue = 190;
6
7 function setup() {
8 createCanvas(windowWidth, windowHeight);
9 colorMode(HSB, 360, 100, 100, 100);
10 background(240, 60, 5);
11
12 for (let i = 0; i < numParticles; i++) {
13 particles.push(new Particle());
14 }
15 }
16
17 function draw() {
18 blendMode(BLEND);
19 noStroke();
20 fill(240, 60, 5, 8);
21 rect(0, 0, width, height);
22
23 blendMode(ADD);
24
25 for (let p of particles) {
26 p.follow();
27 p.interact();
28 p.update();
29 p.edges();
30 p.show();
31 }
32
33 zOff += 0.002;
34 }
35
36 function mousePressed() {
37 baseHue = (baseHue + 65) % 360;
38 }
39
40 function windowResized() {
41 resizeCanvas(windowWidth, windowHeight);
42 blendMode(BLEND);
43 background(240, 60, 5);
44 }
45
46 class Particle {
47 constructor() {
48 this.pos = createVector(random(width), random(height));
49 this.vel = createVector(0, 0);
50 this.acc = createVector(0, 0);
51 this.maxSpeed = random(1.5, 3.5);
52 this.prevPos = this.pos.copy();
53 this.hueOffset = random(-35, 35);
54 this.strokeW = random(0.8, 2.2);
55 }
56
57 follow() {
58 let angle = noise(this.pos.x * noiseScale, this.pos.y * noiseScale, zOff) * TWO_PI * 4;
59 let force = p5.Vector.fromAngle(angle);
60 force.mult(0.25);
61 this.applyForce(force);
62 }
63
64 interact() {
65 if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {
66 let mouse = createVector(mouseX, mouseY);
67 let dir = p5.Vector.sub(this.pos, mouse);
68 let d = dir.mag();
69 if (d < 160) {
70 dir.normalize();
71 let strength = map(d, 0, 160, 1.5, 0);
72 dir.mult(strength);
73 this.applyForce(dir);
74 }
75 }
76 }
77
78 applyForce(force) {
79 this.acc.add(force);
80 }
81
82 update() {
83 this.prevPos.set(this.pos);
84 this.vel.add(this.acc);
85 this.vel.limit(this.maxSpeed);
86 this.pos.add(this.vel);
87 this.acc.mult(0);
88 }
89
90 show() {
91 let h = (baseHue + this.hueOffset + frameCount * 0.15) % 360;
92 if (h < 0) h += 360;
93 let s = map(this.vel.mag(), 0, this.maxSpeed, 50, 90);
94 let b = map(this.vel.mag(), 0, this.maxSpeed, 40, 100);
95
96 stroke(h, s, b, 35);
97 strokeWeight(this.strokeW);
98 line(this.prevPos.x, this.prevPos.y, this.pos.x, this.pos.y);
99 }
100
101 edges() {
102 let wrapped = false;
103 if (this.pos.x > width) { this.pos.x = 0; wrapped = true; }
104 if (this.pos.x < 0) { this.pos.x = width; wrapped = true; }
105 if (this.pos.y > height) { this.pos.y = 0; wrapped = true; }
106 if (this.pos.y < 0) { this.pos.y = height; wrapped = true; }
107
108 if (wrapped) {
109 this.prevPos.set(this.pos);
110 }
111 }
112 }