dopaminejs 2.0.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,345 +0,0 @@
1
- /**
2
- * WebGL Particle System
3
- *
4
- * High-performance particle renderer using WebGL for 10,000+ particles
5
- * Implements IParticleSystem interface
6
- */
7
-
8
- export class WebGLParticleSystem {
9
- constructor(config = {}) {
10
- this.maxParticles = config.maxParticles || 10000;
11
- this.kernel = null;
12
-
13
- // WebGL context and resources
14
- this.canvas = null;
15
- this.gl = null;
16
- this.program = null;
17
- this.particleBuffer = null;
18
-
19
- // Particle data (flat arrays for GPU upload)
20
- this.positions = new Float32Array(this.maxParticles * 2); // x, y
21
- this.velocities = new Float32Array(this.maxParticles * 2); // vx, vy
22
- this.colors = new Float32Array(this.maxParticles * 4); // r, g, b, a
23
- this.sizes = new Float32Array(this.maxParticles);
24
- this.lifetimes = new Float32Array(this.maxParticles);
25
- this.ages = new Float32Array(this.maxParticles);
26
-
27
- this.particleCount = 0;
28
- this.registeredEffects = new Map();
29
- }
30
-
31
- init(kernel) {
32
- this.kernel = kernel;
33
- this._initWebGL();
34
- this._createShaders();
35
- this._createBuffers();
36
- }
37
-
38
- update(dt) {
39
- // Update particle physics
40
- for (let i = 0; i < this.particleCount; i++) {
41
- // Update age
42
- this.ages[i] += dt;
43
-
44
- // Remove dead particles
45
- if (this.ages[i] >= this.lifetimes[i]) {
46
- this._removeParticle(i);
47
- i--; // Recheck this index
48
- continue;
49
- }
50
-
51
- // Update position
52
- this.positions[i * 2] += this.velocities[i * 2] * dt;
53
- this.positions[i * 2 + 1] += this.velocities[i * 2 + 1] * dt;
54
-
55
- // Apply gravity
56
- this.velocities[i * 2 + 1] += 500 * dt; // Gravity
57
-
58
- // Fade out
59
- const lifeRatio = this.ages[i] / this.lifetimes[i];
60
- this.colors[i * 4 + 3] = 1 - lifeRatio; // Alpha fade
61
- }
62
-
63
- // Render
64
- this._render();
65
- }
66
-
67
- destroy() {
68
- if (this.gl) {
69
- this.gl.deleteProgram(this.program);
70
- this.gl.deleteBuffer(this.particleBuffer);
71
- }
72
- if (this.canvas && this.canvas.parentNode) {
73
- this.canvas.parentNode.removeChild(this.canvas);
74
- }
75
- }
76
-
77
- // IParticleSystem interface
78
- registerSprite(key, url) {
79
- // WebGL particles use point sprites, not textures (for simplicity)
80
- console.warn('[WebGLParticles] Sprite registration not yet implemented');
81
- }
82
-
83
- registerEffect(name, callback) {
84
- this.registeredEffects.set(name, callback);
85
- }
86
-
87
- play(name, x, y, ...args) {
88
- const effect = this.registeredEffects.get(name);
89
- if (effect) {
90
- effect.call(this, x, y, ...args);
91
- }
92
- }
93
-
94
- emit(config) {
95
- const {
96
- x = 0,
97
- y = 0,
98
- count = 10,
99
- color = [1, 1, 1, 1],
100
- size = 5,
101
- lifetime = 1,
102
- velocity = { x: 0, y: -100 },
103
- spread = 50
104
- } = config;
105
-
106
- for (let i = 0; i < count; i++) {
107
- if (this.particleCount >= this.maxParticles) break;
108
-
109
- const idx = this.particleCount;
110
-
111
- // Position
112
- this.positions[idx * 2] = x + (Math.random() - 0.5) * spread;
113
- this.positions[idx * 2 + 1] = y + (Math.random() - 0.5) * spread;
114
-
115
- // Velocity
116
- const angle = Math.random() * Math.PI * 2;
117
- const speed = Math.random() * 200 + 100;
118
- this.velocities[idx * 2] = Math.cos(angle) * speed + velocity.x;
119
- this.velocities[idx * 2 + 1] = Math.sin(angle) * speed + velocity.y;
120
-
121
- // Color
122
- this.colors[idx * 4] = color[0];
123
- this.colors[idx * 4 + 1] = color[1];
124
- this.colors[idx * 4 + 2] = color[2];
125
- this.colors[idx * 4 + 3] = color[3];
126
-
127
- // Size and lifetime
128
- this.sizes[idx] = size;
129
- this.lifetimes[idx] = lifetime;
130
- this.ages[idx] = 0;
131
-
132
- this.particleCount++;
133
- }
134
- }
135
-
136
- clear() {
137
- this.particleCount = 0;
138
- }
139
-
140
- // Built-in effects
141
- confetti(x, y, count = 50) {
142
- const colors = [
143
- [1, 0, 0, 1], // Red
144
- [0, 1, 0, 1], // Green
145
- [0, 0, 1, 1], // Blue
146
- [1, 1, 0, 1], // Yellow
147
- [1, 0, 1, 1], // Magenta
148
- ];
149
-
150
- for (let i = 0; i < count; i++) {
151
- this.emit({
152
- x, y,
153
- count: 1,
154
- color: colors[Math.floor(Math.random() * colors.length)],
155
- size: Math.random() * 8 + 4,
156
- lifetime: Math.random() * 2 + 1,
157
- velocity: { x: 0, y: -300 },
158
- spread: 100
159
- });
160
- }
161
- }
162
-
163
- sparkle(x, y, count = 20, color = [1, 1, 1, 1]) {
164
- this.emit({
165
- x, y,
166
- count,
167
- color,
168
- size: 3,
169
- lifetime: 0.5,
170
- velocity: { x: 0, y: -50 },
171
- spread: 30
172
- });
173
- }
174
-
175
- // Private methods
176
- _initWebGL() {
177
- // Create overlay canvas
178
- this.canvas = document.createElement('canvas');
179
- this.canvas.style.cssText = `
180
- position: fixed;
181
- top: 0;
182
- left: 0;
183
- width: 100%;
184
- height: 100%;
185
- pointer-events: none;
186
- z-index: 9999;
187
- `;
188
- this.canvas.width = window.innerWidth;
189
- this.canvas.height = window.innerHeight;
190
- document.body.appendChild(this.canvas);
191
-
192
- // Get WebGL context
193
- this.gl = this.canvas.getContext('webgl') || this.canvas.getContext('experimental-webgl');
194
- if (!this.gl) {
195
- console.error('[WebGLParticles] WebGL not supported');
196
- return;
197
- }
198
-
199
- // Setup WebGL state
200
- this.gl.enable(this.gl.BLEND);
201
- this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
202
-
203
- // Handle resize
204
- window.addEventListener('resize', () => {
205
- this.canvas.width = window.innerWidth;
206
- this.canvas.height = window.innerHeight;
207
- this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
208
- });
209
- }
210
-
211
- _createShaders() {
212
- const vertexShaderSource = `
213
- attribute vec2 a_position;
214
- attribute vec4 a_color;
215
- attribute float a_size;
216
-
217
- uniform vec2 u_resolution;
218
-
219
- varying vec4 v_color;
220
-
221
- void main() {
222
- // Convert from pixels to clip space
223
- vec2 clipSpace = (a_position / u_resolution) * 2.0 - 1.0;
224
- gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
225
- gl_PointSize = a_size;
226
- v_color = a_color;
227
- }
228
- `;
229
-
230
- const fragmentShaderSource = `
231
- precision mediump float;
232
- varying vec4 v_color;
233
-
234
- void main() {
235
- // Circular point sprite
236
- vec2 coord = gl_PointCoord - vec2(0.5);
237
- if (length(coord) > 0.5) {
238
- discard;
239
- }
240
- gl_FragColor = v_color;
241
- }
242
- `;
243
-
244
- const vertexShader = this._compileShader(this.gl.VERTEX_SHADER, vertexShaderSource);
245
- const fragmentShader = this._compileShader(this.gl.FRAGMENT_SHADER, fragmentShaderSource);
246
-
247
- this.program = this.gl.createProgram();
248
- this.gl.attachShader(this.program, vertexShader);
249
- this.gl.attachShader(this.program, fragmentShader);
250
- this.gl.linkProgram(this.program);
251
-
252
- if (!this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS)) {
253
- console.error('[WebGLParticles] Shader program failed to link');
254
- }
255
- }
256
-
257
- _compileShader(type, source) {
258
- const shader = this.gl.createShader(type);
259
- this.gl.shaderSource(shader, source);
260
- this.gl.compileShader(shader);
261
-
262
- if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
263
- console.error('[WebGLParticles] Shader compilation error:', this.gl.getShaderInfoLog(shader));
264
- this.gl.deleteShader(shader);
265
- return null;
266
- }
267
-
268
- return shader;
269
- }
270
-
271
- _createBuffers() {
272
- this.particleBuffer = this.gl.createBuffer();
273
- }
274
-
275
- _render() {
276
- const gl = this.gl;
277
-
278
- // Clear canvas
279
- gl.clearColor(0, 0, 0, 0);
280
- gl.clear(gl.COLOR_BUFFER_BIT);
281
-
282
- if (this.particleCount === 0) return;
283
-
284
- // Use shader program
285
- gl.useProgram(this.program);
286
-
287
- // Set resolution uniform
288
- const resolutionLocation = gl.getUniformLocation(this.program, 'u_resolution');
289
- gl.uniform2f(resolutionLocation, this.canvas.width, this.canvas.height);
290
-
291
- // Interleave particle data for GPU
292
- const vertexData = new Float32Array(this.particleCount * 7); // x, y, r, g, b, a, size
293
- for (let i = 0; i < this.particleCount; i++) {
294
- vertexData[i * 7] = this.positions[i * 2];
295
- vertexData[i * 7 + 1] = this.positions[i * 2 + 1];
296
- vertexData[i * 7 + 2] = this.colors[i * 4];
297
- vertexData[i * 7 + 3] = this.colors[i * 4 + 1];
298
- vertexData[i * 7 + 4] = this.colors[i * 4 + 2];
299
- vertexData[i * 7 + 5] = this.colors[i * 4 + 3];
300
- vertexData[i * 7 + 6] = this.sizes[i];
301
- }
302
-
303
- // Upload to GPU
304
- gl.bindBuffer(gl.ARRAY_BUFFER, this.particleBuffer);
305
- gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.DYNAMIC_DRAW);
306
-
307
- // Setup attributes
308
- const stride = 7 * 4; // 7 floats * 4 bytes
309
-
310
- const positionLocation = gl.getAttribLocation(this.program, 'a_position');
311
- gl.enableVertexAttribArray(positionLocation);
312
- gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, stride, 0);
313
-
314
- const colorLocation = gl.getAttribLocation(this.program, 'a_color');
315
- gl.enableVertexAttribArray(colorLocation);
316
- gl.vertexAttribPointer(colorLocation, 4, gl.FLOAT, false, stride, 2 * 4);
317
-
318
- const sizeLocation = gl.getAttribLocation(this.program, 'a_size');
319
- gl.enableVertexAttribArray(sizeLocation);
320
- gl.vertexAttribPointer(sizeLocation, 1, gl.FLOAT, false, stride, 6 * 4);
321
-
322
- // Draw particles
323
- gl.drawArrays(gl.POINTS, 0, this.particleCount);
324
- }
325
-
326
- _removeParticle(index) {
327
- // Swap with last particle and decrease count
328
- const last = this.particleCount - 1;
329
- if (index !== last) {
330
- // Copy last particle to this index
331
- this.positions[index * 2] = this.positions[last * 2];
332
- this.positions[index * 2 + 1] = this.positions[last * 2 + 1];
333
- this.velocities[index * 2] = this.velocities[last * 2];
334
- this.velocities[index * 2 + 1] = this.velocities[last * 2 + 1];
335
- this.colors[index * 4] = this.colors[last * 4];
336
- this.colors[index * 4 + 1] = this.colors[last * 4 + 1];
337
- this.colors[index * 4 + 2] = this.colors[last * 4 + 2];
338
- this.colors[index * 4 + 3] = this.colors[last * 4 + 3];
339
- this.sizes[index] = this.sizes[last];
340
- this.lifetimes[index] = this.lifetimes[last];
341
- this.ages[index] = this.ages[last];
342
- }
343
- this.particleCount--;
344
- }
345
- }