Source of: Aurora Curl Ribbons, by GPT-5
146 lines, 19 of them comments ยท 4,935 tokens, written in 65 seconds for $0.049
1 let particles = [];
2 let NUM = 1800;
3 let fieldScale = 0.0015; // spatial scale of the noise field
4 let time = 0;
5 let timeSpeed = 0.0016; // speed that the field evolves
6 let paused = false;
7 let fadeAlpha = 10; // trail fade (0..255)
8 let baseSpeed = 55; // global particle speed gain
9 let mouseInfluence = 0.9; // swirl strength near mouse when pressed
10
11 function setup() {
12 createCanvas(windowWidth, windowHeight);
13 colorMode(HSB, 360, 100, 100, 100);
14 background(0);
15 noiseDetail(4, 0.55);
16 initParticles();
17 }
18
19 function initParticles() {
20 particles = [];
21 for (let i = 0; i < NUM; i++) {
22 particles.push(new Particle(random(width), random(height)));
23 }
24 }
25
26 class Particle {
27 constructor(x, y) {
28 this.x = x;
29 this.y = y;
30 this.px = x;
31 this.py = y;
32 this.h = random(360);
33 this.s = 60 + random(30);
34 this.b = 95;
35 this.alpha = 48 + random(28);
36 this.gain = random(0.8, 1.5);
37 this.w = random(0.6, 1.4);
38 }
39
40 step() {
41 // Flow field velocity from curl of noise
42 const v = curl(this.x, this.y, time);
43
44 // Optional mouse swirl influence
45 if (mouseIsPressed) {
46 const dx = this.x - mouseX;
47 const dy = this.y - mouseY;
48 const d2 = dx*dx + dy*dy;
49 const m = 1.0 / (1 + d2 * 0.002);
50 // perpendicular (swirl) component around mouse
51 const sx = -dy * m * mouseInfluence;
52 const sy = dx * m * mouseInfluence;
53 v.x += sx;
54 v.y += sy;
55 }
56
57 // Speed and small jitter
58 v.mult(baseSpeed * this.gain);
59 v.x += (noise(this.x * 0.01, this.y * 0.01, time * 5) - 0.5) * 0.1;
60 v.y += (noise(this.y * 0.01, this.x * 0.01, time * 5) - 0.5) * 0.1;
61
62 // Integrate
63 this.px = this.x;
64 this.py = this.y;
65 this.x += v.x * 0.016; // dt-ish
66 this.y += v.y * 0.016;
67
68 // Wrap with seam fix
69 let wrapped = false;
70 if (this.x < -2) { this.x = width + 2; wrapped = true; }
71 if (this.x > width + 2) { this.x = -2; wrapped = true; }
72 if (this.y < -2) { this.y = height + 2; wrapped = true; }
73 if (this.y > height + 2) { this.y = -2; wrapped = true; }
74 if (wrapped) { this.px = this.x; this.py = this.y; }
75
76 // Evolve hue slowly
77 this.h = (this.h + 0.12 + (v.mag() * 0.0005)) % 360;
78 }
79
80 draw() {
81 const spd = dist(this.x, this.y, this.px, this.py);
82 const w = constrain(map(spd, 0, 4, 0.4, 1.8) * this.w, 0.3, 2.2);
83
84 stroke(this.h, this.s, this.b, this.alpha);
85 strokeWeight(w);
86 line(this.px, this.py, this.x, this.y);
87 }
88 }
89
90 // Curl of 3D noise n(x,y,t)
91 function curl(x, y, t) {
92 const xs = x * fieldScale;
93 const ys = y * fieldScale;
94 const e = 0.01; // finite difference step in noise domain
95
96 const n1 = noise(xs + e, ys, t);
97 const n2 = noise(xs - e, ys, t);
98 const n3 = noise(xs, ys + e, t);
99 const n4 = noise(xs, ys - e, t);
100
101 const dx = n1 - n2;
102 const dy = n3 - n4;
103
104 // Curl(F) of a scalar field-derived vector; divergence-free swirl
105 return createVector(dy, -dx);
106 }
107
108 function draw() {
109 if (paused) return;
110
111 // Gentle fade of trails
112 blendMode(BLEND);
113 noStroke();
114 fill(0, 0, 0, fadeAlpha);
115 rect(0, 0, width, height);
116
117 // Additive blending for luminous ribbons
118 blendMode(ADD);
119
120 for (let p of particles) {
121 p.step();
122 p.draw();
123 }
124
125 // Evolve field time
126 time += timeSpeed;
127 }
128
129 function keyPressed() {
130 if (key === ' '){
131 paused = !paused;
132 if (!paused) loop();
133 }
134 if (key === 'r' || key === 'R') {
135 background(0);
136 initParticles();
137 }
138 if (key === 's' || key === 'S') {
139 saveCanvas('aurora-curl-ribbons', 'png');
140 }
141 }
142
143 function windowResized() {
144 resizeCanvas(windowWidth, windowHeight);
145 background(0);
146 }