Source of: Cosmic Vortices, by Gemini 3.8 Flash
114 lines, no comments ยท 3,244 tokens, written in 18 seconds for $0.012
1 let particles = [];
2 const NUM_PARTICLES = 750;
3 let attractors = [];
4
5 function setup() {
6 createCanvas(windowWidth, windowHeight);
7 colorMode(HSB, 360, 100, 100, 100);
8 background(230, 40, 8);
9
10 for (let i = 0; i < 4; i++) {
11 attractors.push({
12 angle: (TWO_PI / 4) * i,
13 speed: random(0.008, 0.018),
14 radiusX: random(120, 280),
15 radiusY: random(80, 220),
16 x: 0,
17 y: 0
18 });
19 }
20
21 for (let i = 0; i < NUM_PARTICLES; i++) {
22 particles.push(new Particle());
23 }
24 }
25
26 function draw() {
27 background(230, 40, 8, 14);
28
29 let t = frameCount;
30 for (let i = 0; i < attractors.length; i++) {
31 let a = attractors[i];
32 let currentAngle = a.angle + t * a.speed;
33 a.x = width * 0.5 + cos(currentAngle) * a.radiusX * (1 + 0.3 * sin(t * 0.005));
34 a.y = height * 0.5 + sin(currentAngle * 1.3) * a.radiusY * (1 + 0.2 * cos(t * 0.007));
35 }
36
37 for (let p of particles) {
38 p.update(attractors);
39 p.show();
40 }
41 }
42
43 class Particle {
44 constructor() {
45 this.reset();
46 }
47
48 reset() {
49 this.pos = createVector(random(width), random(height));
50 this.prevPos = this.pos.copy();
51 this.vel = p5.Vector.random2D().mult(random(0.5, 2));
52 this.acc = createVector();
53 this.maxSpeed = random(2.5, 5.0);
54 this.baseHue = random(160, 320);
55 this.life = random(150, 320);
56 this.age = 0;
57 }
58
59 update(attractors) {
60 this.prevPos.set(this.pos);
61
62 for (let a of attractors) {
63 let forceVec = createVector(a.x - this.pos.x, a.y - this.pos.y);
64 let distSq = constrain(forceVec.magSq(), 400, 36000);
65 let strength = 260 / distSq;
66 forceVec.normalize().mult(strength);
67
68 let swirl = createVector(-forceVec.y, forceVec.x).mult(1.7);
69 this.acc.add(forceVec);
70 this.acc.add(swirl);
71 }
72
73 if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {
74 let mVec = createVector(mouseX - this.pos.x, mouseY - this.pos.y);
75 let d = mVec.mag();
76 if (d < 220 && d > 1) {
77 mVec.normalize();
78 let mForce = mouseIsPressed ? -4.0 : 1.2;
79 mVec.mult(mForce * (1 - d / 220));
80 this.acc.add(mVec);
81 }
82 }
83
84 let n = noise(this.pos.x * 0.003, this.pos.y * 0.003, frameCount * 0.004);
85 let flowAngle = n * TWO_PI * 4;
86 let flow = p5.Vector.fromAngle(flowAngle).mult(0.35);
87 this.acc.add(flow);
88
89 this.vel.add(this.acc);
90 this.vel.limit(this.maxSpeed);
91 this.pos.add(this.vel);
92 this.acc.mult(0);
93
94 this.age++;
95 if (this.age > this.life || this.pos.x < -20 || this.pos.x > width + 20 || this.pos.y < -20 || this.pos.y > height + 20) {
96 this.reset();
97 }
98 }
99
100 show() {
101 let speed = this.vel.mag();
102 let h = (this.baseHue + speed * 12 + frameCount * 0.15) % 360;
103 let progress = this.age / this.life;
104 let alpha = sin(progress * PI) * 85;
105 stroke(h, 85, 95, alpha);
106 strokeWeight(map(speed, 0, this.maxSpeed, 1, 2.5));
107 line(this.prevPos.x, this.prevPos.y, this.pos.x, this.pos.y);
108 }
109 }
110
111 function windowResized() {
112 resizeCanvas(windowWidth, windowHeight);
113 background(230, 40, 8);
114 }