@guinetik/gcanvas 1.0.4 → 2.0.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.
Files changed (261) hide show
  1. package/dist/CNAME +1 -0
  2. package/dist/aizawa.html +27 -0
  3. package/dist/animations.html +31 -0
  4. package/dist/basic.html +38 -0
  5. package/dist/baskara.html +31 -0
  6. package/dist/bezier.html +35 -0
  7. package/dist/beziersignature.html +29 -0
  8. package/dist/blackhole.html +28 -0
  9. package/dist/blob.html +35 -0
  10. package/dist/clifford.html +25 -0
  11. package/dist/cmb.html +24 -0
  12. package/dist/coordinates.html +698 -0
  13. package/dist/cube3d.html +23 -0
  14. package/dist/dadras.html +26 -0
  15. package/dist/dejong.html +25 -0
  16. package/dist/demos.css +303 -0
  17. package/dist/dino.html +42 -0
  18. package/dist/easing.html +28 -0
  19. package/dist/events.html +195 -0
  20. package/dist/fluent.html +647 -0
  21. package/dist/fluid-simple.html +22 -0
  22. package/dist/fluid.html +37 -0
  23. package/dist/fractals.html +36 -0
  24. package/dist/gameobjects.html +626 -0
  25. package/dist/gcanvas.es.js +14368 -9093
  26. package/dist/gcanvas.es.min.js +1 -1
  27. package/dist/gcanvas.umd.js +1 -1
  28. package/dist/gcanvas.umd.min.js +1 -1
  29. package/dist/genart.html +26 -0
  30. package/dist/gendream.html +26 -0
  31. package/dist/group.html +36 -0
  32. package/dist/halvorsen.html +27 -0
  33. package/dist/home.html +587 -0
  34. package/dist/hyperbolic001.html +23 -0
  35. package/dist/hyperbolic002.html +23 -0
  36. package/dist/hyperbolic003.html +23 -0
  37. package/dist/hyperbolic004.html +23 -0
  38. package/dist/hyperbolic005.html +22 -0
  39. package/dist/index.html +446 -0
  40. package/dist/isometric.html +34 -0
  41. package/dist/js/aizawa.js +425 -0
  42. package/dist/js/animations.js +452 -0
  43. package/dist/js/basic.js +204 -0
  44. package/dist/js/baskara.js +751 -0
  45. package/dist/js/bezier.js +692 -0
  46. package/dist/js/beziersignature.js +241 -0
  47. package/dist/js/blackhole/accretiondisk.obj.js +379 -0
  48. package/dist/js/blackhole/blackhole.obj.js +318 -0
  49. package/dist/js/blackhole/index.js +409 -0
  50. package/dist/js/blackhole/particle.js +56 -0
  51. package/dist/js/blackhole/starfield.obj.js +218 -0
  52. package/dist/js/blob.js +2276 -0
  53. package/dist/js/clifford.js +236 -0
  54. package/dist/js/cmb.js +594 -0
  55. package/dist/js/coordinates.js +840 -0
  56. package/dist/js/cube3d.js +789 -0
  57. package/dist/js/dadras.js +405 -0
  58. package/dist/js/dejong.js +257 -0
  59. package/dist/js/dino.js +1420 -0
  60. package/dist/js/easing.js +477 -0
  61. package/dist/js/fluent.js +183 -0
  62. package/dist/js/fluid-simple.js +253 -0
  63. package/dist/js/fluid.js +527 -0
  64. package/dist/js/fractals.js +932 -0
  65. package/dist/js/fractalworker.js +93 -0
  66. package/dist/js/gameobjects.js +176 -0
  67. package/dist/js/genart.js +268 -0
  68. package/dist/js/gendream.js +209 -0
  69. package/dist/js/group.js +140 -0
  70. package/dist/js/halvorsen.js +405 -0
  71. package/dist/js/hyperbolic001.js +310 -0
  72. package/dist/js/hyperbolic002.js +388 -0
  73. package/dist/js/hyperbolic003.js +319 -0
  74. package/dist/js/hyperbolic004.js +345 -0
  75. package/dist/js/hyperbolic005.js +340 -0
  76. package/dist/js/info-toggle.js +25 -0
  77. package/dist/js/isometric.js +851 -0
  78. package/dist/js/kerr.js +1547 -0
  79. package/dist/js/lavalamp.js +590 -0
  80. package/dist/js/layout.js +354 -0
  81. package/dist/js/lorenz.js +425 -0
  82. package/dist/js/mondrian.js +285 -0
  83. package/dist/js/opacity.js +275 -0
  84. package/dist/js/painter.js +484 -0
  85. package/dist/js/particles-showcase.js +514 -0
  86. package/dist/js/particles.js +299 -0
  87. package/dist/js/patterns.js +397 -0
  88. package/dist/js/penrose/artifact.js +69 -0
  89. package/dist/js/penrose/blackhole.js +121 -0
  90. package/dist/js/penrose/constants.js +73 -0
  91. package/dist/js/penrose/game.js +943 -0
  92. package/dist/js/penrose/lore.js +278 -0
  93. package/dist/js/penrose/penrosescene.js +892 -0
  94. package/dist/js/penrose/ship.js +216 -0
  95. package/dist/js/penrose/sounds.js +211 -0
  96. package/dist/js/penrose/voidparticle.js +55 -0
  97. package/dist/js/penrose/voidscene.js +258 -0
  98. package/dist/js/penrose/voidship.js +144 -0
  99. package/dist/js/penrose/wormhole.js +46 -0
  100. package/dist/js/pipeline.js +555 -0
  101. package/dist/js/plane3d.js +256 -0
  102. package/dist/js/platformer.js +1579 -0
  103. package/dist/js/rossler.js +480 -0
  104. package/dist/js/scene.js +304 -0
  105. package/dist/js/scenes.js +320 -0
  106. package/dist/js/schrodinger.js +706 -0
  107. package/dist/js/schwarzschild.js +1015 -0
  108. package/dist/js/shapes.js +628 -0
  109. package/dist/js/space/alien.js +171 -0
  110. package/dist/js/space/boom.js +98 -0
  111. package/dist/js/space/boss.js +353 -0
  112. package/dist/js/space/buff.js +73 -0
  113. package/dist/js/space/bullet.js +102 -0
  114. package/dist/js/space/constants.js +85 -0
  115. package/dist/js/space/game.js +1884 -0
  116. package/dist/js/space/hud.js +112 -0
  117. package/dist/js/space/laserbeam.js +179 -0
  118. package/dist/js/space/lightning.js +277 -0
  119. package/dist/js/space/minion.js +192 -0
  120. package/dist/js/space/missile.js +212 -0
  121. package/dist/js/space/player.js +430 -0
  122. package/dist/js/space/powerup.js +90 -0
  123. package/dist/js/space/starfield.js +58 -0
  124. package/dist/js/space/starpower.js +90 -0
  125. package/dist/js/spacetime.js +559 -0
  126. package/dist/js/sphere3d.js +229 -0
  127. package/dist/js/sprite.js +473 -0
  128. package/dist/js/starfaux/config.js +118 -0
  129. package/dist/js/starfaux/enemy.js +353 -0
  130. package/dist/js/starfaux/hud.js +78 -0
  131. package/dist/js/starfaux/index.js +482 -0
  132. package/dist/js/starfaux/laser.js +182 -0
  133. package/dist/js/starfaux/player.js +468 -0
  134. package/dist/js/starfaux/terrain.js +560 -0
  135. package/dist/js/study001.js +275 -0
  136. package/dist/js/study002.js +366 -0
  137. package/dist/js/study003.js +331 -0
  138. package/dist/js/study004.js +389 -0
  139. package/dist/js/study005.js +209 -0
  140. package/dist/js/study006.js +194 -0
  141. package/dist/js/study007.js +192 -0
  142. package/dist/js/study008.js +413 -0
  143. package/dist/js/svgtween.js +204 -0
  144. package/dist/js/tde/accretiondisk.js +471 -0
  145. package/dist/js/tde/blackhole.js +219 -0
  146. package/dist/js/tde/blackholescene.js +209 -0
  147. package/dist/js/tde/config.js +59 -0
  148. package/dist/js/tde/index.js +820 -0
  149. package/dist/js/tde/jets.js +290 -0
  150. package/dist/js/tde/lensedstarfield.js +154 -0
  151. package/dist/js/tde/tdestar.js +297 -0
  152. package/dist/js/tde/tidalstream.js +372 -0
  153. package/dist/js/tde_old/blackhole.obj.js +354 -0
  154. package/dist/js/tde_old/debris.obj.js +791 -0
  155. package/dist/js/tde_old/flare.obj.js +239 -0
  156. package/dist/js/tde_old/index.js +448 -0
  157. package/dist/js/tde_old/star.obj.js +812 -0
  158. package/dist/js/tetris/config.js +157 -0
  159. package/dist/js/tetris/grid.js +286 -0
  160. package/dist/js/tetris/index.js +1195 -0
  161. package/dist/js/tetris/renderer.js +634 -0
  162. package/dist/js/tetris/tetrominos.js +280 -0
  163. package/dist/js/thomas.js +394 -0
  164. package/dist/js/tiles.js +312 -0
  165. package/dist/js/tweendemo.js +79 -0
  166. package/dist/js/visibility.js +102 -0
  167. package/dist/kerr.html +28 -0
  168. package/dist/lavalamp.html +27 -0
  169. package/dist/layouts.html +37 -0
  170. package/dist/logo.svg +4 -0
  171. package/dist/loop.html +84 -0
  172. package/dist/lorenz.html +27 -0
  173. package/dist/mondrian.html +32 -0
  174. package/dist/og_image.png +0 -0
  175. package/dist/opacity.html +36 -0
  176. package/dist/painter.html +39 -0
  177. package/dist/particles-showcase.html +28 -0
  178. package/dist/particles.html +24 -0
  179. package/dist/patterns.html +33 -0
  180. package/dist/penrose-game.html +31 -0
  181. package/dist/pipeline.html +737 -0
  182. package/dist/plane3d.html +24 -0
  183. package/dist/platformer.html +43 -0
  184. package/dist/rossler.html +27 -0
  185. package/dist/scene-interactivity-test.html +220 -0
  186. package/dist/scene.html +33 -0
  187. package/dist/scenes.html +96 -0
  188. package/dist/schrodinger.html +27 -0
  189. package/dist/schwarzschild.html +27 -0
  190. package/dist/shapes.html +16 -0
  191. package/dist/space.html +85 -0
  192. package/dist/spacetime.html +27 -0
  193. package/dist/sphere3d.html +24 -0
  194. package/dist/sprite.html +18 -0
  195. package/dist/starfaux.html +22 -0
  196. package/dist/study001.html +23 -0
  197. package/dist/study002.html +23 -0
  198. package/dist/study003.html +23 -0
  199. package/dist/study004.html +23 -0
  200. package/dist/study005.html +22 -0
  201. package/dist/study006.html +24 -0
  202. package/dist/study007.html +24 -0
  203. package/dist/study008.html +22 -0
  204. package/dist/svgtween.html +29 -0
  205. package/dist/tde.html +28 -0
  206. package/dist/tetris3d.html +25 -0
  207. package/dist/thomas.html +27 -0
  208. package/dist/tiles.html +28 -0
  209. package/dist/transforms.html +400 -0
  210. package/dist/tween.html +45 -0
  211. package/dist/visibility.html +33 -0
  212. package/package.json +1 -1
  213. package/readme.md +30 -22
  214. package/src/game/objects/go.js +7 -0
  215. package/src/game/objects/index.js +2 -0
  216. package/src/game/objects/isometric-scene.js +53 -3
  217. package/src/game/objects/layoutscene.js +57 -0
  218. package/src/game/objects/mask.js +241 -0
  219. package/src/game/objects/scene.js +19 -0
  220. package/src/game/objects/wrapper.js +14 -2
  221. package/src/game/pipeline.js +17 -0
  222. package/src/game/ui/button.js +101 -16
  223. package/src/game/ui/theme.js +0 -6
  224. package/src/game/ui/togglebutton.js +25 -14
  225. package/src/game/ui/tooltip.js +12 -4
  226. package/src/index.js +3 -0
  227. package/src/io/gesture.js +409 -0
  228. package/src/io/index.js +4 -1
  229. package/src/io/keys.js +9 -1
  230. package/src/io/screen.js +476 -0
  231. package/src/math/attractors.js +664 -0
  232. package/src/math/heat.js +106 -0
  233. package/src/math/index.js +1 -0
  234. package/src/mixins/draggable.js +15 -19
  235. package/src/painter/painter.shapes.js +11 -5
  236. package/src/particle/particle-system.js +165 -1
  237. package/src/physics/index.js +26 -0
  238. package/src/physics/physics-updaters.js +333 -0
  239. package/src/physics/physics.js +375 -0
  240. package/src/shapes/image.js +5 -5
  241. package/src/shapes/index.js +2 -0
  242. package/src/shapes/parallelogram.js +147 -0
  243. package/src/shapes/righttriangle.js +115 -0
  244. package/src/shapes/svg.js +281 -100
  245. package/src/shapes/text.js +22 -6
  246. package/src/shapes/transformable.js +5 -0
  247. package/src/sound/effects.js +807 -0
  248. package/src/sound/index.js +13 -0
  249. package/src/webgl/index.js +7 -0
  250. package/src/webgl/shaders/clifford-point-shaders.js +131 -0
  251. package/src/webgl/shaders/dejong-point-shaders.js +131 -0
  252. package/src/webgl/shaders/point-sprite-shaders.js +152 -0
  253. package/src/webgl/webgl-clifford-renderer.js +477 -0
  254. package/src/webgl/webgl-dejong-renderer.js +472 -0
  255. package/src/webgl/webgl-line-renderer.js +391 -0
  256. package/src/webgl/webgl-particle-renderer.js +410 -0
  257. package/types/index.d.ts +30 -2
  258. package/types/io.d.ts +217 -0
  259. package/types/physics.d.ts +299 -0
  260. package/types/shapes.d.ts +8 -0
  261. package/types/webgl.d.ts +188 -109
@@ -0,0 +1,410 @@
1
+ /**
2
+ * WebGLParticleRenderer - GPU-accelerated particle rendering via point sprites
3
+ *
4
+ * Renders particles using WebGL GL_POINTS with gl_PointSize for GPU acceleration.
5
+ * Works alongside the existing ParticleSystem which handles state management,
6
+ * pooling, and updaters on the CPU.
7
+ *
8
+ * Features:
9
+ * - Pre-allocated buffers for zero-allocation rendering
10
+ * - Multiple shape presets (circle, glow, square)
11
+ * - Additive or alpha blending modes
12
+ * - Automatic fallback detection
13
+ * - Compositing onto Canvas 2D
14
+ *
15
+ * @example
16
+ * const renderer = new WebGLParticleRenderer(10000);
17
+ * renderer.updateParticles(projectedParticles);
18
+ * renderer.render(particleCount);
19
+ * renderer.compositeOnto(ctx, 0, 0);
20
+ */
21
+
22
+ import {
23
+ POINT_SPRITE_VERTEX,
24
+ POINT_SPRITE_CIRCLE_FRAGMENT,
25
+ POINT_SPRITE_GLOW_FRAGMENT,
26
+ POINT_SPRITE_SQUARE_FRAGMENT,
27
+ POINT_SPRITE_SOFT_SQUARE_FRAGMENT,
28
+ } from './shaders/point-sprite-shaders.js';
29
+
30
+ export class WebGLParticleRenderer {
31
+ /**
32
+ * Create a WebGL particle renderer
33
+ * @param {number} maxParticles - Maximum particle capacity (pre-allocated)
34
+ * @param {Object} options - Configuration options
35
+ * @param {number} options.width - Initial canvas width
36
+ * @param {number} options.height - Initial canvas height
37
+ * @param {string} options.shape - Particle shape: 'circle', 'glow', 'square', 'softSquare'
38
+ * @param {string} options.blendMode - Blend mode: 'additive' or 'alpha'
39
+ */
40
+ constructor(maxParticles = 10000, options = {}) {
41
+ this.maxParticles = maxParticles;
42
+ this.width = options.width || 800;
43
+ this.height = options.height || 600;
44
+ this.shape = options.shape || 'circle';
45
+ this.blendMode = options.blendMode || 'alpha';
46
+
47
+ // Create offscreen canvas
48
+ this.canvas = document.createElement('canvas');
49
+ this.canvas.width = this.width;
50
+ this.canvas.height = this.height;
51
+
52
+ // Get WebGL context
53
+ this.gl = this.canvas.getContext('webgl', {
54
+ alpha: true,
55
+ premultipliedAlpha: true,
56
+ antialias: false, // Points don't need AA
57
+ preserveDrawingBuffer: true,
58
+ });
59
+
60
+ if (!this.gl) {
61
+ console.warn('WebGL not available for particle rendering');
62
+ this.available = false;
63
+ return;
64
+ }
65
+
66
+ this.available = true;
67
+
68
+ // Pre-allocate typed arrays (reused each frame)
69
+ this._positions = new Float32Array(maxParticles * 2);
70
+ this._sizes = new Float32Array(maxParticles);
71
+ this._colors = new Float32Array(maxParticles * 4);
72
+
73
+ // Setup WebGL
74
+ this._initGL();
75
+ this._createBuffers();
76
+ this._compileShaders();
77
+ this._setupBlending();
78
+ }
79
+
80
+ /**
81
+ * Check if WebGL is available
82
+ * @returns {boolean}
83
+ */
84
+ isAvailable() {
85
+ return this.available;
86
+ }
87
+
88
+ /**
89
+ * Initialize WebGL state
90
+ * @private
91
+ */
92
+ _initGL() {
93
+ const gl = this.gl;
94
+ gl.viewport(0, 0, this.width, this.height);
95
+ gl.enable(gl.BLEND);
96
+ }
97
+
98
+ /**
99
+ * Setup blending mode
100
+ * @private
101
+ */
102
+ _setupBlending() {
103
+ const gl = this.gl;
104
+
105
+ if (this.blendMode === 'additive') {
106
+ // Additive blending (screen/lighter mode)
107
+ // With premultiplied alpha: src + dest
108
+ gl.blendFunc(gl.ONE, gl.ONE);
109
+ } else {
110
+ // Standard alpha blending with premultiplied alpha
111
+ gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Set blend mode
117
+ * @param {string} mode - 'additive' or 'alpha'
118
+ */
119
+ setBlendMode(mode) {
120
+ this.blendMode = mode;
121
+ if (this.available) {
122
+ this._setupBlending();
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Create GPU buffers for particle data
128
+ * @private
129
+ */
130
+ _createBuffers() {
131
+ const gl = this.gl;
132
+
133
+ // Position buffer (vec2 per particle)
134
+ this.positionBuffer = gl.createBuffer();
135
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer);
136
+ gl.bufferData(gl.ARRAY_BUFFER, this._positions, gl.DYNAMIC_DRAW);
137
+
138
+ // Size buffer (float per particle)
139
+ this.sizeBuffer = gl.createBuffer();
140
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.sizeBuffer);
141
+ gl.bufferData(gl.ARRAY_BUFFER, this._sizes, gl.DYNAMIC_DRAW);
142
+
143
+ // Color buffer (vec4 per particle)
144
+ this.colorBuffer = gl.createBuffer();
145
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer);
146
+ gl.bufferData(gl.ARRAY_BUFFER, this._colors, gl.DYNAMIC_DRAW);
147
+ }
148
+
149
+ /**
150
+ * Compile point sprite shaders
151
+ * @private
152
+ */
153
+ _compileShaders() {
154
+ const gl = this.gl;
155
+
156
+ // Select fragment shader based on shape
157
+ let fragmentSource;
158
+ switch (this.shape) {
159
+ case 'glow':
160
+ fragmentSource = POINT_SPRITE_GLOW_FRAGMENT;
161
+ break;
162
+ case 'square':
163
+ fragmentSource = POINT_SPRITE_SQUARE_FRAGMENT;
164
+ break;
165
+ case 'softSquare':
166
+ fragmentSource = POINT_SPRITE_SOFT_SQUARE_FRAGMENT;
167
+ break;
168
+ case 'circle':
169
+ default:
170
+ fragmentSource = POINT_SPRITE_CIRCLE_FRAGMENT;
171
+ break;
172
+ }
173
+
174
+ // Compile vertex shader
175
+ const vertexShader = gl.createShader(gl.VERTEX_SHADER);
176
+ gl.shaderSource(vertexShader, POINT_SPRITE_VERTEX);
177
+ gl.compileShader(vertexShader);
178
+
179
+ if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
180
+ console.error('Vertex shader error:', gl.getShaderInfoLog(vertexShader));
181
+ this.available = false;
182
+ return;
183
+ }
184
+
185
+ // Compile fragment shader
186
+ const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
187
+ gl.shaderSource(fragmentShader, fragmentSource);
188
+ gl.compileShader(fragmentShader);
189
+
190
+ if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
191
+ console.error('Fragment shader error:', gl.getShaderInfoLog(fragmentShader));
192
+ this.available = false;
193
+ return;
194
+ }
195
+
196
+ // Link program
197
+ this.program = gl.createProgram();
198
+ gl.attachShader(this.program, vertexShader);
199
+ gl.attachShader(this.program, fragmentShader);
200
+ gl.linkProgram(this.program);
201
+
202
+ if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
203
+ console.error('Program link error:', gl.getProgramInfoLog(this.program));
204
+ this.available = false;
205
+ return;
206
+ }
207
+
208
+ gl.useProgram(this.program);
209
+
210
+ // Get attribute locations
211
+ this.aPosition = gl.getAttribLocation(this.program, 'aPosition');
212
+ this.aSize = gl.getAttribLocation(this.program, 'aSize');
213
+ this.aColor = gl.getAttribLocation(this.program, 'aColor');
214
+
215
+ // Get uniform locations
216
+ this.uResolution = gl.getUniformLocation(this.program, 'uResolution');
217
+
218
+ // Set initial resolution
219
+ gl.uniform2f(this.uResolution, this.width, this.height);
220
+
221
+ // Clean up shader objects
222
+ gl.deleteShader(vertexShader);
223
+ gl.deleteShader(fragmentShader);
224
+ }
225
+
226
+ /**
227
+ * Change particle shape (recompiles fragment shader)
228
+ * @param {string} shape - 'circle', 'glow', 'square', or 'softSquare'
229
+ */
230
+ setShape(shape) {
231
+ if (shape === this.shape) return;
232
+ this.shape = shape;
233
+
234
+ if (this.available) {
235
+ // Delete old program
236
+ if (this.program) {
237
+ this.gl.deleteProgram(this.program);
238
+ }
239
+ // Recompile with new fragment shader
240
+ this._compileShaders();
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Resize the renderer
246
+ * @param {number} width - New width
247
+ * @param {number} height - New height
248
+ */
249
+ resize(width, height) {
250
+ this.width = width;
251
+ this.height = height;
252
+ this.canvas.width = width;
253
+ this.canvas.height = height;
254
+
255
+ if (this.available) {
256
+ const gl = this.gl;
257
+ gl.viewport(0, 0, width, height);
258
+ gl.useProgram(this.program);
259
+ gl.uniform2f(this.uResolution, width, height);
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Update particle data in GPU buffers
265
+ *
266
+ * @param {Array} particles - Array of projected particles with screen coords
267
+ * Each particle should have: { x, y, size, color: {r, g, b, a} }
268
+ * Colors should be 0-255 for RGB, 0-1 for alpha
269
+ * @returns {number} Number of particles updated
270
+ */
271
+ updateParticles(particles) {
272
+ if (!this.available) return 0;
273
+
274
+ const count = Math.min(particles.length, this.maxParticles);
275
+ const gl = this.gl;
276
+
277
+ // Fill typed arrays
278
+ for (let i = 0; i < count; i++) {
279
+ const p = particles[i];
280
+ const i2 = i * 2;
281
+ const i4 = i * 4;
282
+
283
+ // Position (screen coords)
284
+ this._positions[i2] = p.x;
285
+ this._positions[i2 + 1] = p.y;
286
+
287
+ // Size
288
+ this._sizes[i] = p.size;
289
+
290
+ // Color (normalize RGB to 0-1)
291
+ const color = p.color;
292
+ // For premultiplied alpha, multiply RGB by alpha
293
+ const a = color.a !== undefined ? color.a : 1;
294
+ const r = (color.r / 255) * a;
295
+ const g = (color.g / 255) * a;
296
+ const b = (color.b / 255) * a;
297
+
298
+ this._colors[i4] = r;
299
+ this._colors[i4 + 1] = g;
300
+ this._colors[i4 + 2] = b;
301
+ this._colors[i4 + 3] = a;
302
+ }
303
+
304
+ // Upload to GPU via bufferSubData (faster than bufferData for partial updates)
305
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer);
306
+ gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._positions.subarray(0, count * 2));
307
+
308
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.sizeBuffer);
309
+ gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._sizes.subarray(0, count));
310
+
311
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer);
312
+ gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._colors.subarray(0, count * 4));
313
+
314
+ return count;
315
+ }
316
+
317
+ /**
318
+ * Clear the canvas
319
+ * @param {number} r - Red (0-1)
320
+ * @param {number} g - Green (0-1)
321
+ * @param {number} b - Blue (0-1)
322
+ * @param {number} a - Alpha (0-1)
323
+ */
324
+ clear(r = 0, g = 0, b = 0, a = 0) {
325
+ if (!this.available) return;
326
+ const gl = this.gl;
327
+ gl.clearColor(r, g, b, a);
328
+ gl.clear(gl.COLOR_BUFFER_BIT);
329
+ }
330
+
331
+ /**
332
+ * Render particles to the WebGL canvas
333
+ * @param {number} count - Number of particles to render
334
+ */
335
+ render(count) {
336
+ if (!this.available || count === 0) return;
337
+
338
+ const gl = this.gl;
339
+
340
+ gl.useProgram(this.program);
341
+
342
+ // Bind position attribute
343
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer);
344
+ gl.enableVertexAttribArray(this.aPosition);
345
+ gl.vertexAttribPointer(this.aPosition, 2, gl.FLOAT, false, 0, 0);
346
+
347
+ // Bind size attribute
348
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.sizeBuffer);
349
+ gl.enableVertexAttribArray(this.aSize);
350
+ gl.vertexAttribPointer(this.aSize, 1, gl.FLOAT, false, 0, 0);
351
+
352
+ // Bind color attribute
353
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer);
354
+ gl.enableVertexAttribArray(this.aColor);
355
+ gl.vertexAttribPointer(this.aColor, 4, gl.FLOAT, false, 0, 0);
356
+
357
+ // Draw all particles in a single call
358
+ gl.drawArrays(gl.POINTS, 0, count);
359
+ }
360
+
361
+ /**
362
+ * Composite the WebGL canvas onto a 2D canvas context
363
+ * @param {CanvasRenderingContext2D} ctx - Target 2D context
364
+ * @param {number} x - X position
365
+ * @param {number} y - Y position
366
+ * @param {number} [width] - Optional width
367
+ * @param {number} [height] - Optional height
368
+ */
369
+ compositeOnto(ctx, x = 0, y = 0, width, height) {
370
+ if (!this.available) return;
371
+ ctx.drawImage(
372
+ this.canvas,
373
+ x, y,
374
+ width ?? this.canvas.width,
375
+ height ?? this.canvas.height
376
+ );
377
+ }
378
+
379
+ /**
380
+ * Get the WebGL canvas element
381
+ * @returns {HTMLCanvasElement}
382
+ */
383
+ getCanvas() {
384
+ return this.canvas;
385
+ }
386
+
387
+ /**
388
+ * Destroy the renderer and free resources
389
+ */
390
+ destroy() {
391
+ if (!this.available) return;
392
+
393
+ const gl = this.gl;
394
+
395
+ // Delete program
396
+ if (this.program) {
397
+ gl.deleteProgram(this.program);
398
+ }
399
+
400
+ // Delete buffers
401
+ gl.deleteBuffer(this.positionBuffer);
402
+ gl.deleteBuffer(this.sizeBuffer);
403
+ gl.deleteBuffer(this.colorBuffer);
404
+
405
+ // Clear typed arrays
406
+ this._positions = null;
407
+ this._sizes = null;
408
+ this._colors = null;
409
+ }
410
+ }
package/types/index.d.ts CHANGED
@@ -437,12 +437,40 @@ export {
437
437
  Updaters
438
438
  } from './particle';
439
439
 
440
+ // ==========================================================================
441
+ // Physics Module
442
+ // ==========================================================================
443
+
444
+ export {
445
+ Physics,
446
+ PhysicsUpdaters,
447
+ CollisionResult,
448
+ ForceResult,
449
+ VelocityResult,
450
+ ElasticCollisionResult,
451
+ Bounds3D,
452
+ Sphere as PhysicsSphere,
453
+ Position3D,
454
+ PhysicsParticle
455
+ } from './physics';
456
+
440
457
  // ==========================================================================
441
458
  // WebGL Module (Optional)
442
459
  // ==========================================================================
443
460
 
444
461
  export {
445
462
  WebGLRenderer,
446
- WebGLRendererOptions,
447
- SPHERE_SHADERS
463
+ WebGLParticleRenderer,
464
+ WebGLLineRenderer,
465
+ WebGLDeJongRenderer,
466
+ WebGLCliffordRenderer,
467
+ DEJONG_MAX_ITERATIONS,
468
+ DEJONG_POINT_VERTEX,
469
+ DEJONG_POINT_FRAGMENTS,
470
+ CLIFFORD_MAX_ITERATIONS,
471
+ CLIFFORD_POINT_VERTEX,
472
+ CLIFFORD_POINT_FRAGMENTS,
473
+ SPHERE_SHADERS,
474
+ WebGLBlendMode,
475
+ PointSpriteShape
448
476
  } from './webgl';
package/types/io.d.ts CHANGED
@@ -186,3 +186,220 @@ export class Input {
186
186
  */
187
187
  static init(game: Game): void;
188
188
  }
189
+
190
+ // ==========================================================================
191
+ // Screen Detection
192
+ // ==========================================================================
193
+
194
+ /**
195
+ * Screen/device detection and responsive utilities.
196
+ * Static class that tracks screen size, device type, and orientation.
197
+ *
198
+ * @example
199
+ * // Get responsive values
200
+ * const scaleFactor = Screen.responsive(1.5, 2, 3);
201
+ *
202
+ * // Check device type
203
+ * if (Screen.isMobile) {
204
+ * // Mobile-specific logic
205
+ * }
206
+ *
207
+ * // Listen for changes
208
+ * game.events.on('devicechange', (e) => {
209
+ * console.log('Device type changed:', e.isMobile);
210
+ * });
211
+ */
212
+ export class Screen {
213
+ /** Mobile breakpoint in pixels (default: 768) */
214
+ static MOBILE_BREAKPOINT: number;
215
+ /** Tablet breakpoint in pixels (default: 1024) */
216
+ static TABLET_BREAKPOINT: number;
217
+
218
+ /** Current screen/window width */
219
+ static width: number;
220
+ /** Current screen/window height */
221
+ static height: number;
222
+ /** Device pixel ratio for high-DPI displays */
223
+ static pixelRatio: number;
224
+
225
+ /** Whether device is mobile (width <= MOBILE_BREAKPOINT) */
226
+ static isMobile: boolean;
227
+ /** Whether device is tablet (MOBILE_BREAKPOINT < width <= TABLET_BREAKPOINT) */
228
+ static isTablet: boolean;
229
+ /** Whether device is desktop (width > TABLET_BREAKPOINT) */
230
+ static isDesktop: boolean;
231
+ /** Whether device has touch capability */
232
+ static hasTouch: boolean;
233
+
234
+ /** Current orientation: 'portrait' or 'landscape' */
235
+ static orientation: 'portrait' | 'landscape';
236
+ /** Whether screen is in portrait mode */
237
+ static isPortrait: boolean;
238
+ /** Whether screen is in landscape mode */
239
+ static isLandscape: boolean;
240
+
241
+ /** Whether wake lock is currently enabled (requested by user) */
242
+ static wakeLockEnabled: boolean;
243
+ /** Whether the Wake Lock API is supported in this browser */
244
+ static wakeLockSupported: boolean;
245
+
246
+ /**
247
+ * Initialize screen detection for a game instance.
248
+ * @param game - Game instance
249
+ */
250
+ static init(game: Game): void;
251
+
252
+ /**
253
+ * Get a responsive value based on device type.
254
+ * @param mobile - Value for mobile devices
255
+ * @param tablet - Value for tablet devices (defaults to mobile)
256
+ * @param desktop - Value for desktop devices (defaults to tablet)
257
+ * @returns The appropriate value for current device
258
+ */
259
+ static responsive<T>(mobile: T, tablet?: T, desktop?: T): T;
260
+
261
+ /**
262
+ * Get a value scaled by pixel ratio for high-DPI displays.
263
+ * @param value - Base value to scale
264
+ * @returns Value multiplied by device pixel ratio
265
+ */
266
+ static scaled(value: number): number;
267
+
268
+ /**
269
+ * Check if device is likely touch-primary (mobile/tablet with touch).
270
+ * @returns True if device is touch-primary
271
+ */
272
+ static isTouchPrimary(): boolean;
273
+
274
+ /**
275
+ * Get the smaller dimension of the screen.
276
+ * @returns The smaller of width or height
277
+ */
278
+ static minDimension(): number;
279
+
280
+ /**
281
+ * Get the larger dimension of the screen.
282
+ * @returns The larger of width or height
283
+ */
284
+ static maxDimension(): number;
285
+
286
+ /**
287
+ * Get the aspect ratio (width / height).
288
+ * @returns The aspect ratio
289
+ */
290
+ static aspectRatio(): number;
291
+
292
+ /**
293
+ * Check if screen matches a CSS media query.
294
+ * @param query - CSS media query string
295
+ * @returns True if query matches
296
+ */
297
+ static matches(query: string): boolean;
298
+
299
+ /**
300
+ * Check if user prefers reduced motion.
301
+ * @returns True if user prefers reduced motion
302
+ */
303
+ static prefersReducedMotion(): boolean;
304
+
305
+ /**
306
+ * Check if user prefers dark color scheme.
307
+ * @returns True if user prefers dark mode
308
+ */
309
+ static prefersDarkMode(): boolean;
310
+
311
+ // Wake Lock API
312
+
313
+ /**
314
+ * Request a wake lock to prevent the screen from sleeping.
315
+ * Useful for games/simulations that should keep the display on.
316
+ * The lock is automatically re-acquired when the page becomes visible.
317
+ * @returns True if wake lock was successfully acquired
318
+ */
319
+ static requestWakeLock(): Promise<boolean>;
320
+
321
+ /**
322
+ * Release the wake lock, allowing the screen to sleep normally.
323
+ * Call this when your game/simulation stops or pauses.
324
+ */
325
+ static releaseWakeLock(): Promise<void>;
326
+
327
+ /**
328
+ * Check if wake lock is currently active.
329
+ * @returns True if wake lock is held
330
+ */
331
+ static isWakeLockActive(): boolean;
332
+ }
333
+
334
+ // ==========================================================================
335
+ // Gesture Recognition
336
+ // ==========================================================================
337
+
338
+ /**
339
+ * Gesture options for configuring zoom, pan, and tap behavior.
340
+ */
341
+ export interface GestureOptions {
342
+ /** Callback for zoom: (delta, centerX, centerY) => void. delta > 0 = zoom in */
343
+ onZoom?: (delta: number, centerX: number, centerY: number) => void;
344
+ /** Callback for pan: (dx, dy) => void */
345
+ onPan?: (dx: number, dy: number) => void;
346
+ /** Callback for tap/click: (x, y) => void */
347
+ onTap?: (x: number, y: number) => void;
348
+ /** Callback when drag starts: (x, y) => void */
349
+ onDragStart?: (x: number, y: number) => void;
350
+ /** Callback when drag ends: () => void */
351
+ onDragEnd?: () => void;
352
+ /** Zoom sensitivity for mouse wheel (default: 0.1) */
353
+ wheelZoomFactor?: number;
354
+ /** Zoom sensitivity for pinch gesture (default: 1) */
355
+ pinchZoomFactor?: number;
356
+ /** Scale factor for pan deltas (default: 1) */
357
+ panScale?: number;
358
+ /** Max movement in pixels to still count as tap (default: 10) */
359
+ tapThreshold?: number;
360
+ /** Max duration in ms for tap (default: 300) */
361
+ tapTimeout?: number;
362
+ /** Prevent default browser behavior (default: true) */
363
+ preventDefault?: boolean;
364
+ }
365
+
366
+ /**
367
+ * High-level gesture recognition for zoom, pan, and tap.
368
+ * Works seamlessly across mouse and touch input.
369
+ *
370
+ * @example
371
+ * const gesture = new Gesture(canvas, {
372
+ * onZoom: (delta, cx, cy) => {
373
+ * this.zoom *= delta > 0 ? 1.1 : 0.9;
374
+ * },
375
+ * onPan: (dx, dy) => {
376
+ * this.offsetX += dx;
377
+ * this.offsetY += dy;
378
+ * },
379
+ * onTap: (x, y) => {
380
+ * this.handleClick(x, y);
381
+ * }
382
+ * });
383
+ *
384
+ * // Cleanup
385
+ * gesture.destroy();
386
+ */
387
+ export class Gesture {
388
+ /** The canvas element gestures are attached to */
389
+ canvas: HTMLCanvasElement;
390
+ /** Whether a drag is currently in progress */
391
+ readonly isDragging: boolean;
392
+
393
+ /**
394
+ * Create a new Gesture handler.
395
+ * @param canvas - Canvas element to attach gestures to
396
+ * @param options - Configuration options
397
+ */
398
+ constructor(canvas: HTMLCanvasElement, options?: GestureOptions);
399
+
400
+ /**
401
+ * Remove all event listeners and clean up.
402
+ * Call this when the gesture handler is no longer needed.
403
+ */
404
+ destroy(): void;
405
+ }