@guinetik/gcanvas 1.0.5 → 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 (78) hide show
  1. package/dist/aizawa.html +27 -0
  2. package/dist/clifford.html +25 -0
  3. package/dist/cmb.html +24 -0
  4. package/dist/dadras.html +26 -0
  5. package/dist/dejong.html +25 -0
  6. package/dist/gcanvas.es.js +5130 -372
  7. package/dist/gcanvas.es.min.js +1 -1
  8. package/dist/gcanvas.umd.js +1 -1
  9. package/dist/gcanvas.umd.min.js +1 -1
  10. package/dist/halvorsen.html +27 -0
  11. package/dist/index.html +96 -48
  12. package/dist/js/aizawa.js +425 -0
  13. package/dist/js/bezier.js +5 -5
  14. package/dist/js/clifford.js +236 -0
  15. package/dist/js/cmb.js +594 -0
  16. package/dist/js/dadras.js +405 -0
  17. package/dist/js/dejong.js +257 -0
  18. package/dist/js/halvorsen.js +405 -0
  19. package/dist/js/isometric.js +34 -46
  20. package/dist/js/lorenz.js +425 -0
  21. package/dist/js/painter.js +8 -8
  22. package/dist/js/rossler.js +480 -0
  23. package/dist/js/schrodinger.js +314 -18
  24. package/dist/js/thomas.js +394 -0
  25. package/dist/lorenz.html +27 -0
  26. package/dist/rossler.html +27 -0
  27. package/dist/scene-interactivity-test.html +220 -0
  28. package/dist/thomas.html +27 -0
  29. package/package.json +1 -1
  30. package/readme.md +30 -22
  31. package/src/game/objects/go.js +7 -0
  32. package/src/game/objects/index.js +2 -0
  33. package/src/game/objects/isometric-scene.js +53 -3
  34. package/src/game/objects/layoutscene.js +57 -0
  35. package/src/game/objects/mask.js +241 -0
  36. package/src/game/objects/scene.js +19 -0
  37. package/src/game/objects/wrapper.js +14 -2
  38. package/src/game/pipeline.js +17 -0
  39. package/src/game/ui/button.js +101 -16
  40. package/src/game/ui/theme.js +0 -6
  41. package/src/game/ui/togglebutton.js +25 -14
  42. package/src/game/ui/tooltip.js +12 -4
  43. package/src/index.js +3 -0
  44. package/src/io/gesture.js +409 -0
  45. package/src/io/index.js +4 -1
  46. package/src/io/keys.js +9 -1
  47. package/src/io/screen.js +476 -0
  48. package/src/math/attractors.js +664 -0
  49. package/src/math/heat.js +106 -0
  50. package/src/math/index.js +1 -0
  51. package/src/mixins/draggable.js +15 -19
  52. package/src/painter/painter.shapes.js +11 -5
  53. package/src/particle/particle-system.js +165 -1
  54. package/src/physics/index.js +26 -0
  55. package/src/physics/physics-updaters.js +333 -0
  56. package/src/physics/physics.js +375 -0
  57. package/src/shapes/image.js +5 -5
  58. package/src/shapes/index.js +2 -0
  59. package/src/shapes/parallelogram.js +147 -0
  60. package/src/shapes/righttriangle.js +115 -0
  61. package/src/shapes/svg.js +281 -100
  62. package/src/shapes/text.js +22 -6
  63. package/src/shapes/transformable.js +5 -0
  64. package/src/sound/effects.js +807 -0
  65. package/src/sound/index.js +13 -0
  66. package/src/webgl/index.js +7 -0
  67. package/src/webgl/shaders/clifford-point-shaders.js +131 -0
  68. package/src/webgl/shaders/dejong-point-shaders.js +131 -0
  69. package/src/webgl/shaders/point-sprite-shaders.js +152 -0
  70. package/src/webgl/webgl-clifford-renderer.js +477 -0
  71. package/src/webgl/webgl-dejong-renderer.js +472 -0
  72. package/src/webgl/webgl-line-renderer.js +391 -0
  73. package/src/webgl/webgl-particle-renderer.js +410 -0
  74. package/types/index.d.ts +30 -2
  75. package/types/io.d.ts +217 -0
  76. package/types/physics.d.ts +299 -0
  77. package/types/shapes.d.ts +8 -0
  78. package/types/webgl.d.ts +188 -109
@@ -0,0 +1,472 @@
1
+ /**
2
+ * @module webgl/webgl-dejong-renderer
3
+ * @description WebGLDeJongRenderer - procedural De Jong attractor renderer.
4
+ *
5
+ * This renderer draws the De Jong attractor using GL_POINTS and performs
6
+ * the iterative map directly in the vertex shader (GPU).
7
+ *
8
+ * It renders into an offscreen WebGL canvas which can be composited onto
9
+ * the main 2D canvas using `compositeOnto()`.
10
+ *
11
+ * Design goals:
12
+ * - No CPU point streaming (static seed buffer)
13
+ * - Uniform-driven parameters (a,b,c,d), iteration count, zoom, transform
14
+ * - Compatible with existing point sprite fragment shapes (circle/glow/etc.)
15
+ *
16
+ * @example
17
+ * import { WebGLDeJongRenderer } from "@guinetik/gcanvas";
18
+ *
19
+ * const r = new WebGLDeJongRenderer(1 << 18, { width: 800, height: 600, shape: "glow" });
20
+ * r.setParams({ a: -2.0, b: -2.0, c: -1.2, d: 2.0 });
21
+ * r.setIterations(100);
22
+ * r.setZoom(1);
23
+ * r.setPointSize(1.0);
24
+ * r.render();
25
+ * r.compositeOnto(ctx, 0, 0);
26
+ */
27
+
28
+ import { DEJONG_POINT_FRAGMENTS, DEJONG_POINT_VERTEX, DEJONG_MAX_ITERATIONS } from "./shaders/dejong-point-shaders.js";
29
+
30
+ /**
31
+ * @typedef {"alpha"|"additive"} WebGLBlendMode
32
+ * @typedef {"circle"|"glow"|"square"|"softSquare"} PointSpriteShape
33
+ */
34
+
35
+ export class WebGLDeJongRenderer {
36
+ /**
37
+ * @param {number} seedCount - Number of seeds (points) to render.
38
+ * @param {Object} [options]
39
+ * @param {number} [options.width=800] - Initial canvas width
40
+ * @param {number} [options.height=600] - Initial canvas height
41
+ * @param {PointSpriteShape} [options.shape="glow"] - Point sprite fragment shape
42
+ * @param {WebGLBlendMode} [options.blendMode="additive"] - WebGL blending mode
43
+ * @param {number} [options.pointSize=1] - gl_PointSize in pixels
44
+ * @param {number} [options.pointScale=0.5] - Mapping from attractor space to clip space
45
+ * @param {number} [options.iterations=100] - Iteration count (0..DEJONG_MAX_ITERATIONS)
46
+ * @param {{a:number,b:number,c:number,d:number}} [options.params] - De Jong parameters
47
+ * @param {{r:number,g:number,b:number,a:number}} [options.color] - RGBA (0..1)
48
+ */
49
+ constructor(seedCount = 1 << 18, options = {}) {
50
+ this.seedCount = seedCount;
51
+
52
+ this.width = options.width ?? 800;
53
+ this.height = options.height ?? 600;
54
+
55
+ this.shape = options.shape ?? "glow";
56
+ this.blendMode = options.blendMode ?? "additive";
57
+
58
+ this.pointSize = options.pointSize ?? 1.0;
59
+ this.pointScale = options.pointScale ?? 0.5;
60
+
61
+ this.iterations = Math.max(0, Math.min(DEJONG_MAX_ITERATIONS, options.iterations ?? 100));
62
+ this.params = {
63
+ a: options.params?.a ?? -2.0,
64
+ b: options.params?.b ?? -2.0,
65
+ c: options.params?.c ?? -1.2,
66
+ d: options.params?.d ?? 2.0,
67
+ };
68
+
69
+ this.zoom = 1.0;
70
+ this.transform = WebGLDeJongRenderer.identityMat3();
71
+
72
+ this.color = options.color ?? { r: 1, g: 1, b: 1, a: 0.12 };
73
+
74
+ // Color mode + ramp settings (Lorenz-like)
75
+ // 0 = flat color, 1 = speed→hue ramp
76
+ this.colorMode = options.colorMode ?? 0;
77
+ this.hueRange = options.hueRange ?? { minHue: 30, maxHue: 200 };
78
+ this.maxSpeed = options.maxSpeed ?? 1.0;
79
+ this.saturation = options.saturation ?? 0.85; // 0..1
80
+ this.lightness = options.lightness ?? 0.55; // 0..1
81
+ this.alpha = options.alpha ?? this.color.a ?? 0.12; // 0..1
82
+ this.hueShiftSpeed = options.hueShiftSpeed ?? 0; // degrees per second
83
+
84
+ // Offscreen canvas
85
+ this.canvas = document.createElement("canvas");
86
+ this.canvas.width = this.width;
87
+ this.canvas.height = this.height;
88
+
89
+ // Premultiplied alpha for correct compositing to Canvas 2D
90
+ this.gl = this.canvas.getContext("webgl", {
91
+ alpha: true,
92
+ premultipliedAlpha: true,
93
+ antialias: false,
94
+ preserveDrawingBuffer: true,
95
+ });
96
+
97
+ if (!this.gl) {
98
+ console.warn("WebGL not available for DeJong renderer");
99
+ this.available = false;
100
+ return;
101
+ }
102
+
103
+ this.available = true;
104
+
105
+ this._initGL();
106
+ this._createSeedBuffer(seedCount);
107
+ this._compileProgram();
108
+ this._setupBlending();
109
+ this._applyStaticUniforms();
110
+ }
111
+
112
+ /**
113
+ * @returns {boolean}
114
+ */
115
+ isAvailable() {
116
+ return Boolean(this.available);
117
+ }
118
+
119
+ /**
120
+ * Identity 3x3 matrix (column-major).
121
+ * @returns {Float32Array}
122
+ */
123
+ static identityMat3() {
124
+ return new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]);
125
+ }
126
+
127
+ /**
128
+ * 2D rotation matrix (column-major mat3).
129
+ * @param {number} angle - Radians
130
+ * @returns {Float32Array}
131
+ */
132
+ static rotationMat3(angle) {
133
+ const c = Math.cos(angle);
134
+ const s = Math.sin(angle);
135
+ return new Float32Array([c, s, 0, -s, c, 0, 0, 0, 1]);
136
+ }
137
+
138
+ /**
139
+ * @private
140
+ */
141
+ _initGL() {
142
+ const gl = this.gl;
143
+ gl.viewport(0, 0, this.width, this.height);
144
+ gl.enable(gl.BLEND);
145
+ }
146
+
147
+ /**
148
+ * @private
149
+ * @param {number} seedCount
150
+ */
151
+ _createSeedBuffer(seedCount) {
152
+ const gl = this.gl;
153
+
154
+ this._seeds = new Float32Array(seedCount * 2);
155
+ for (let i = 0; i < this._seeds.length; i++) {
156
+ this._seeds[i] = Math.random() * 2 - 1;
157
+ }
158
+
159
+ this.seedBuffer = gl.createBuffer();
160
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.seedBuffer);
161
+ gl.bufferData(gl.ARRAY_BUFFER, this._seeds, gl.STATIC_DRAW);
162
+ }
163
+
164
+ /**
165
+ * Re-generate random seeds.
166
+ */
167
+ regenerateSeeds() {
168
+ if (!this.available) return;
169
+ for (let i = 0; i < this._seeds.length; i++) {
170
+ this._seeds[i] = Math.random() * 2 - 1;
171
+ }
172
+ const gl = this.gl;
173
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.seedBuffer);
174
+ gl.bufferData(gl.ARRAY_BUFFER, this._seeds, gl.STATIC_DRAW);
175
+ }
176
+
177
+ /**
178
+ * Change seed count (recreates buffers).
179
+ * @param {number} seedCount
180
+ */
181
+ setSeedCount(seedCount) {
182
+ if (!this.available) return;
183
+ if (seedCount === this.seedCount) return;
184
+
185
+ this.seedCount = seedCount;
186
+
187
+ const gl = this.gl;
188
+ if (this.seedBuffer) gl.deleteBuffer(this.seedBuffer);
189
+
190
+ this._createSeedBuffer(seedCount);
191
+ }
192
+
193
+ /**
194
+ * @private
195
+ */
196
+ _compileProgram() {
197
+ const gl = this.gl;
198
+
199
+ const fragmentSource = DEJONG_POINT_FRAGMENTS[this.shape] ?? DEJONG_POINT_FRAGMENTS.glow;
200
+
201
+ const vertexShader = gl.createShader(gl.VERTEX_SHADER);
202
+ gl.shaderSource(vertexShader, DEJONG_POINT_VERTEX);
203
+ gl.compileShader(vertexShader);
204
+ if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
205
+ console.error("DeJong vertex shader error:", gl.getShaderInfoLog(vertexShader));
206
+ this.available = false;
207
+ return;
208
+ }
209
+
210
+ const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
211
+ gl.shaderSource(fragmentShader, fragmentSource);
212
+ gl.compileShader(fragmentShader);
213
+ if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
214
+ console.error("DeJong fragment shader error:", gl.getShaderInfoLog(fragmentShader));
215
+ this.available = false;
216
+ return;
217
+ }
218
+
219
+ this.program = gl.createProgram();
220
+ gl.attachShader(this.program, vertexShader);
221
+ gl.attachShader(this.program, fragmentShader);
222
+ gl.linkProgram(this.program);
223
+ if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
224
+ console.error("DeJong program link error:", gl.getProgramInfoLog(this.program));
225
+ this.available = false;
226
+ return;
227
+ }
228
+
229
+ gl.useProgram(this.program);
230
+
231
+ // Locations
232
+ this.aPosition = gl.getAttribLocation(this.program, "aPosition");
233
+
234
+ this.uTime = gl.getUniformLocation(this.program, "uTime");
235
+ this.uParams = gl.getUniformLocation(this.program, "uParams");
236
+ this.uIterations = gl.getUniformLocation(this.program, "uIterations");
237
+ this.uTransform = gl.getUniformLocation(this.program, "uTransform");
238
+ this.uZoom = gl.getUniformLocation(this.program, "uZoom");
239
+ this.uPointScale = gl.getUniformLocation(this.program, "uPointScale");
240
+ this.uPointSize = gl.getUniformLocation(this.program, "uPointSize");
241
+ this.uColorMode = gl.getUniformLocation(this.program, "uColorMode");
242
+ this.uColor = gl.getUniformLocation(this.program, "uColor");
243
+ this.uHueRange = gl.getUniformLocation(this.program, "uHueRange");
244
+ this.uMaxSpeed = gl.getUniformLocation(this.program, "uMaxSpeed");
245
+ this.uSaturation = gl.getUniformLocation(this.program, "uSaturation");
246
+ this.uLightness = gl.getUniformLocation(this.program, "uLightness");
247
+ this.uAlpha = gl.getUniformLocation(this.program, "uAlpha");
248
+ this.uHueOffset = gl.getUniformLocation(this.program, "uHueOffset");
249
+
250
+ // Cleanup shader objects
251
+ gl.deleteShader(vertexShader);
252
+ gl.deleteShader(fragmentShader);
253
+ }
254
+
255
+ /**
256
+ * @private
257
+ */
258
+ _setupBlending() {
259
+ const gl = this.gl;
260
+ if (this.blendMode === "additive") {
261
+ gl.blendFunc(gl.ONE, gl.ONE);
262
+ } else {
263
+ gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Set WebGL blend mode.
269
+ * @param {WebGLBlendMode} mode
270
+ */
271
+ setBlendMode(mode) {
272
+ this.blendMode = mode;
273
+ if (this.available) this._setupBlending();
274
+ }
275
+
276
+ /**
277
+ * Set point sprite shape (recompiles fragment shader).
278
+ * @param {PointSpriteShape} shape
279
+ */
280
+ setShape(shape) {
281
+ if (shape === this.shape) return;
282
+ this.shape = shape;
283
+
284
+ if (!this.available) return;
285
+ const gl = this.gl;
286
+ if (this.program) gl.deleteProgram(this.program);
287
+ this._compileProgram();
288
+ this._setupBlending();
289
+ this._applyStaticUniforms();
290
+ }
291
+
292
+ /**
293
+ * @private
294
+ */
295
+ _applyStaticUniforms() {
296
+ if (!this.available) return;
297
+ const gl = this.gl;
298
+ gl.useProgram(this.program);
299
+ if (this.uPointScale) gl.uniform1f(this.uPointScale, this.pointScale);
300
+ if (this.uPointSize) gl.uniform1f(this.uPointSize, this.pointSize);
301
+ }
302
+
303
+ /**
304
+ * Set color mode.
305
+ * @param {0|1} mode - 0=flat, 1=speed→hue
306
+ */
307
+ setColorMode(mode) {
308
+ this.colorMode = mode === 1 ? 1 : 0;
309
+ }
310
+
311
+ /**
312
+ * Set Lorenz-like color ramp settings.
313
+ * @param {Object} options
314
+ * @param {number} options.minHue - degrees (fast)
315
+ * @param {number} options.maxHue - degrees (slow)
316
+ * @param {number} options.maxSpeed - speed normalization threshold
317
+ * @param {number} options.saturation - 0..1
318
+ * @param {number} options.lightness - 0..1
319
+ * @param {number} options.alpha - 0..1
320
+ * @param {number} options.hueShiftSpeed - degrees per second
321
+ */
322
+ setColorRamp(options = {}) {
323
+ if (options.minHue !== undefined) this.hueRange.minHue = options.minHue;
324
+ if (options.maxHue !== undefined) this.hueRange.maxHue = options.maxHue;
325
+ if (options.maxSpeed !== undefined) this.maxSpeed = options.maxSpeed;
326
+ if (options.saturation !== undefined) this.saturation = options.saturation;
327
+ if (options.lightness !== undefined) this.lightness = options.lightness;
328
+ if (options.alpha !== undefined) this.alpha = options.alpha;
329
+ if (options.hueShiftSpeed !== undefined) this.hueShiftSpeed = options.hueShiftSpeed;
330
+ }
331
+
332
+ /**
333
+ * Set De Jong parameters.
334
+ * @param {{a:number,b:number,c:number,d:number}} params
335
+ */
336
+ setParams(params) {
337
+ this.params = { ...this.params, ...params };
338
+ }
339
+
340
+ /**
341
+ * Set iteration count (clamped to shader max).
342
+ * @param {number} iterations
343
+ */
344
+ setIterations(iterations) {
345
+ this.iterations = Math.max(0, Math.min(DEJONG_MAX_ITERATIONS, Math.floor(iterations)));
346
+ }
347
+
348
+ /**
349
+ * Set zoom factor.
350
+ * @param {number} zoom
351
+ */
352
+ setZoom(zoom) {
353
+ this.zoom = zoom;
354
+ }
355
+
356
+ /**
357
+ * Set transform matrix (mat3, column-major).
358
+ * @param {Float32Array} mat3
359
+ */
360
+ setTransform(mat3) {
361
+ this.transform = mat3;
362
+ }
363
+
364
+ /**
365
+ * Set point size in pixels.
366
+ * @param {number} size
367
+ */
368
+ setPointSize(size) {
369
+ this.pointSize = size;
370
+ if (this.available && this.uPointSize) {
371
+ this.gl.useProgram(this.program);
372
+ this.gl.uniform1f(this.uPointSize, size);
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Set color (0..1 RGBA).
378
+ * @param {{r:number,g:number,b:number,a:number}} color
379
+ */
380
+ setColor(color) {
381
+ this.color = { ...this.color, ...color };
382
+ }
383
+
384
+ /**
385
+ * Resize the renderer.
386
+ * @param {number} width
387
+ * @param {number} height
388
+ */
389
+ resize(width, height) {
390
+ this.width = width;
391
+ this.height = height;
392
+ this.canvas.width = width;
393
+ this.canvas.height = height;
394
+ if (this.available) {
395
+ this.gl.viewport(0, 0, width, height);
396
+ }
397
+ }
398
+
399
+ /**
400
+ * Clear the WebGL canvas.
401
+ * @param {number} r - 0..1
402
+ * @param {number} g - 0..1
403
+ * @param {number} b - 0..1
404
+ * @param {number} a - 0..1
405
+ */
406
+ clear(r = 0, g = 0, b = 0, a = 0) {
407
+ if (!this.available) return;
408
+ const gl = this.gl;
409
+ gl.clearColor(r, g, b, a);
410
+ gl.clear(gl.COLOR_BUFFER_BIT);
411
+ }
412
+
413
+ /**
414
+ * Render one frame.
415
+ * @param {number} timeSeconds - Time in seconds (uTime)
416
+ */
417
+ render(timeSeconds = 0) {
418
+ if (!this.available) return;
419
+
420
+ const gl = this.gl;
421
+ gl.useProgram(this.program);
422
+
423
+ // Update uniforms
424
+ if (this.uTime) gl.uniform1f(this.uTime, timeSeconds);
425
+ if (this.uParams) gl.uniform4f(this.uParams, this.params.a, this.params.b, this.params.c, this.params.d);
426
+ if (this.uIterations) gl.uniform1i(this.uIterations, this.iterations);
427
+ if (this.uTransform) gl.uniformMatrix3fv(this.uTransform, false, this.transform);
428
+ if (this.uZoom) gl.uniform1f(this.uZoom, this.zoom);
429
+ if (this.uPointScale) gl.uniform1f(this.uPointScale, this.pointScale);
430
+ if (this.uPointSize) gl.uniform1f(this.uPointSize, this.pointSize);
431
+ if (this.uColor) gl.uniform4f(this.uColor, this.color.r, this.color.g, this.color.b, this.color.a);
432
+ if (this.uColorMode) gl.uniform1i(this.uColorMode, this.colorMode);
433
+ if (this.uHueRange) gl.uniform2f(this.uHueRange, this.hueRange.minHue, this.hueRange.maxHue);
434
+ if (this.uMaxSpeed) gl.uniform1f(this.uMaxSpeed, this.maxSpeed);
435
+ if (this.uSaturation) gl.uniform1f(this.uSaturation, this.saturation);
436
+ if (this.uLightness) gl.uniform1f(this.uLightness, this.lightness);
437
+ if (this.uAlpha) gl.uniform1f(this.uAlpha, this.alpha);
438
+ if (this.uHueOffset) gl.uniform1f(this.uHueOffset, (timeSeconds * this.hueShiftSpeed) % 360);
439
+
440
+ // Bind seeds
441
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.seedBuffer);
442
+ gl.enableVertexAttribArray(this.aPosition);
443
+ gl.vertexAttribPointer(this.aPosition, 2, gl.FLOAT, false, 0, 0);
444
+
445
+ gl.drawArrays(gl.POINTS, 0, this.seedCount);
446
+ }
447
+
448
+ /**
449
+ * Composite WebGL canvas onto a 2D canvas context.
450
+ * @param {CanvasRenderingContext2D} ctx
451
+ * @param {number} [x=0]
452
+ * @param {number} [y=0]
453
+ * @param {number} [width]
454
+ * @param {number} [height]
455
+ */
456
+ compositeOnto(ctx, x = 0, y = 0, width, height) {
457
+ if (!this.available) return;
458
+ ctx.drawImage(this.canvas, x, y, width ?? this.canvas.width, height ?? this.canvas.height);
459
+ }
460
+
461
+ /**
462
+ * Destroy and free resources.
463
+ */
464
+ destroy() {
465
+ if (!this.available) return;
466
+ const gl = this.gl;
467
+ if (this.program) gl.deleteProgram(this.program);
468
+ if (this.seedBuffer) gl.deleteBuffer(this.seedBuffer);
469
+ this._seeds = null;
470
+ }
471
+ }
472
+