@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.
- package/dist/aizawa.html +27 -0
- package/dist/clifford.html +25 -0
- package/dist/cmb.html +24 -0
- package/dist/dadras.html +26 -0
- package/dist/dejong.html +25 -0
- package/dist/gcanvas.es.js +5130 -372
- package/dist/gcanvas.es.min.js +1 -1
- package/dist/gcanvas.umd.js +1 -1
- package/dist/gcanvas.umd.min.js +1 -1
- package/dist/halvorsen.html +27 -0
- package/dist/index.html +96 -48
- package/dist/js/aizawa.js +425 -0
- package/dist/js/bezier.js +5 -5
- package/dist/js/clifford.js +236 -0
- package/dist/js/cmb.js +594 -0
- package/dist/js/dadras.js +405 -0
- package/dist/js/dejong.js +257 -0
- package/dist/js/halvorsen.js +405 -0
- package/dist/js/isometric.js +34 -46
- package/dist/js/lorenz.js +425 -0
- package/dist/js/painter.js +8 -8
- package/dist/js/rossler.js +480 -0
- package/dist/js/schrodinger.js +314 -18
- package/dist/js/thomas.js +394 -0
- package/dist/lorenz.html +27 -0
- package/dist/rossler.html +27 -0
- package/dist/scene-interactivity-test.html +220 -0
- package/dist/thomas.html +27 -0
- package/package.json +1 -1
- package/readme.md +30 -22
- package/src/game/objects/go.js +7 -0
- package/src/game/objects/index.js +2 -0
- package/src/game/objects/isometric-scene.js +53 -3
- package/src/game/objects/layoutscene.js +57 -0
- package/src/game/objects/mask.js +241 -0
- package/src/game/objects/scene.js +19 -0
- package/src/game/objects/wrapper.js +14 -2
- package/src/game/pipeline.js +17 -0
- package/src/game/ui/button.js +101 -16
- package/src/game/ui/theme.js +0 -6
- package/src/game/ui/togglebutton.js +25 -14
- package/src/game/ui/tooltip.js +12 -4
- package/src/index.js +3 -0
- package/src/io/gesture.js +409 -0
- package/src/io/index.js +4 -1
- package/src/io/keys.js +9 -1
- package/src/io/screen.js +476 -0
- package/src/math/attractors.js +664 -0
- package/src/math/heat.js +106 -0
- package/src/math/index.js +1 -0
- package/src/mixins/draggable.js +15 -19
- package/src/painter/painter.shapes.js +11 -5
- package/src/particle/particle-system.js +165 -1
- package/src/physics/index.js +26 -0
- package/src/physics/physics-updaters.js +333 -0
- package/src/physics/physics.js +375 -0
- package/src/shapes/image.js +5 -5
- package/src/shapes/index.js +2 -0
- package/src/shapes/parallelogram.js +147 -0
- package/src/shapes/righttriangle.js +115 -0
- package/src/shapes/svg.js +281 -100
- package/src/shapes/text.js +22 -6
- package/src/shapes/transformable.js +5 -0
- package/src/sound/effects.js +807 -0
- package/src/sound/index.js +13 -0
- package/src/webgl/index.js +7 -0
- package/src/webgl/shaders/clifford-point-shaders.js +131 -0
- package/src/webgl/shaders/dejong-point-shaders.js +131 -0
- package/src/webgl/shaders/point-sprite-shaders.js +152 -0
- package/src/webgl/webgl-clifford-renderer.js +477 -0
- package/src/webgl/webgl-dejong-renderer.js +472 -0
- package/src/webgl/webgl-line-renderer.js +391 -0
- package/src/webgl/webgl-particle-renderer.js +410 -0
- package/types/index.d.ts +30 -2
- package/types/io.d.ts +217 -0
- package/types/physics.d.ts +299 -0
- package/types/shapes.d.ts +8 -0
- package/types/webgl.d.ts +188 -109
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebGLLineRenderer - GPU-accelerated line rendering
|
|
3
|
+
*
|
|
4
|
+
* Renders line segments using WebGL GL_LINES for high performance.
|
|
5
|
+
* Designed for particle trails, attractor visualizations, and similar
|
|
6
|
+
* effects that require many line segments.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Pre-allocated buffers for zero-allocation rendering
|
|
10
|
+
* - Additive or alpha blending modes
|
|
11
|
+
* - Per-vertex colors for gradient trails
|
|
12
|
+
* - Compositing onto Canvas 2D
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const renderer = new WebGLLineRenderer(50000); // 50k line segments
|
|
16
|
+
* renderer.updateLines(lineData);
|
|
17
|
+
* renderer.render(segmentCount);
|
|
18
|
+
* renderer.compositeOnto(ctx, 0, 0);
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
// Line vertex shader
|
|
22
|
+
const LINE_VERTEX_SHADER = `
|
|
23
|
+
precision highp float;
|
|
24
|
+
|
|
25
|
+
attribute vec2 aPosition; // Screen position (pixels)
|
|
26
|
+
attribute vec4 aColor; // RGBA color (0-1 range, premultiplied)
|
|
27
|
+
|
|
28
|
+
varying vec4 vColor;
|
|
29
|
+
|
|
30
|
+
uniform vec2 uResolution; // Canvas dimensions
|
|
31
|
+
|
|
32
|
+
void main() {
|
|
33
|
+
// Convert from pixel coords to clip space (-1 to 1)
|
|
34
|
+
vec2 clipPos = (aPosition / uResolution) * 2.0 - 1.0;
|
|
35
|
+
clipPos.y = -clipPos.y; // Flip Y (canvas Y is down, GL Y is up)
|
|
36
|
+
|
|
37
|
+
gl_Position = vec4(clipPos, 0.0, 1.0);
|
|
38
|
+
vColor = aColor;
|
|
39
|
+
}
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
// Line fragment shader
|
|
43
|
+
const LINE_FRAGMENT_SHADER = `
|
|
44
|
+
precision mediump float;
|
|
45
|
+
|
|
46
|
+
varying vec4 vColor;
|
|
47
|
+
|
|
48
|
+
void main() {
|
|
49
|
+
gl_FragColor = vColor;
|
|
50
|
+
}
|
|
51
|
+
`;
|
|
52
|
+
|
|
53
|
+
export class WebGLLineRenderer {
|
|
54
|
+
/**
|
|
55
|
+
* Create a WebGL line renderer
|
|
56
|
+
* @param {number} maxSegments - Maximum number of line segments (pre-allocated)
|
|
57
|
+
* @param {Object} options - Configuration options
|
|
58
|
+
* @param {number} options.width - Initial canvas width
|
|
59
|
+
* @param {number} options.height - Initial canvas height
|
|
60
|
+
* @param {string} options.blendMode - Blend mode: 'additive' or 'alpha'
|
|
61
|
+
*/
|
|
62
|
+
constructor(maxSegments = 10000, options = {}) {
|
|
63
|
+
this.maxSegments = maxSegments;
|
|
64
|
+
this.width = options.width || 800;
|
|
65
|
+
this.height = options.height || 600;
|
|
66
|
+
this.blendMode = options.blendMode || 'additive';
|
|
67
|
+
|
|
68
|
+
// Create offscreen canvas
|
|
69
|
+
this.canvas = document.createElement('canvas');
|
|
70
|
+
this.canvas.width = this.width;
|
|
71
|
+
this.canvas.height = this.height;
|
|
72
|
+
|
|
73
|
+
// Get WebGL context
|
|
74
|
+
this.gl = this.canvas.getContext('webgl', {
|
|
75
|
+
alpha: true,
|
|
76
|
+
premultipliedAlpha: true,
|
|
77
|
+
antialias: true,
|
|
78
|
+
preserveDrawingBuffer: true,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (!this.gl) {
|
|
82
|
+
console.warn('WebGL not available for line rendering');
|
|
83
|
+
this.available = false;
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.available = true;
|
|
88
|
+
|
|
89
|
+
// Pre-allocate typed arrays (2 vertices per segment)
|
|
90
|
+
// Each vertex: x, y (position) + r, g, b, a (color)
|
|
91
|
+
this._positions = new Float32Array(maxSegments * 4); // 2 vertices * 2 coords
|
|
92
|
+
this._colors = new Float32Array(maxSegments * 8); // 2 vertices * 4 color components
|
|
93
|
+
|
|
94
|
+
// Setup WebGL
|
|
95
|
+
this._initGL();
|
|
96
|
+
this._createBuffers();
|
|
97
|
+
this._compileShaders();
|
|
98
|
+
this._setupBlending();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Check if WebGL is available
|
|
103
|
+
* @returns {boolean}
|
|
104
|
+
*/
|
|
105
|
+
isAvailable() {
|
|
106
|
+
return this.available;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Initialize WebGL state
|
|
111
|
+
* @private
|
|
112
|
+
*/
|
|
113
|
+
_initGL() {
|
|
114
|
+
const gl = this.gl;
|
|
115
|
+
gl.viewport(0, 0, this.width, this.height);
|
|
116
|
+
gl.enable(gl.BLEND);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Setup blending mode
|
|
121
|
+
* @private
|
|
122
|
+
*/
|
|
123
|
+
_setupBlending() {
|
|
124
|
+
const gl = this.gl;
|
|
125
|
+
|
|
126
|
+
if (this.blendMode === 'additive') {
|
|
127
|
+
// Additive blending (screen/lighter mode)
|
|
128
|
+
gl.blendFunc(gl.ONE, gl.ONE);
|
|
129
|
+
} else {
|
|
130
|
+
// Standard alpha blending with premultiplied alpha
|
|
131
|
+
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Set blend mode
|
|
137
|
+
* @param {string} mode - 'additive' or 'alpha'
|
|
138
|
+
*/
|
|
139
|
+
setBlendMode(mode) {
|
|
140
|
+
this.blendMode = mode;
|
|
141
|
+
if (this.available) {
|
|
142
|
+
this._setupBlending();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Create GPU buffers
|
|
148
|
+
* @private
|
|
149
|
+
*/
|
|
150
|
+
_createBuffers() {
|
|
151
|
+
const gl = this.gl;
|
|
152
|
+
|
|
153
|
+
// Position buffer (vec2 per vertex, 2 vertices per segment)
|
|
154
|
+
this.positionBuffer = gl.createBuffer();
|
|
155
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer);
|
|
156
|
+
gl.bufferData(gl.ARRAY_BUFFER, this._positions, gl.DYNAMIC_DRAW);
|
|
157
|
+
|
|
158
|
+
// Color buffer (vec4 per vertex, 2 vertices per segment)
|
|
159
|
+
this.colorBuffer = gl.createBuffer();
|
|
160
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer);
|
|
161
|
+
gl.bufferData(gl.ARRAY_BUFFER, this._colors, gl.DYNAMIC_DRAW);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Compile shaders
|
|
166
|
+
* @private
|
|
167
|
+
*/
|
|
168
|
+
_compileShaders() {
|
|
169
|
+
const gl = this.gl;
|
|
170
|
+
|
|
171
|
+
// Compile vertex shader
|
|
172
|
+
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
|
|
173
|
+
gl.shaderSource(vertexShader, LINE_VERTEX_SHADER);
|
|
174
|
+
gl.compileShader(vertexShader);
|
|
175
|
+
|
|
176
|
+
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
|
|
177
|
+
console.error('Line vertex shader error:', gl.getShaderInfoLog(vertexShader));
|
|
178
|
+
this.available = false;
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Compile fragment shader
|
|
183
|
+
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
|
|
184
|
+
gl.shaderSource(fragmentShader, LINE_FRAGMENT_SHADER);
|
|
185
|
+
gl.compileShader(fragmentShader);
|
|
186
|
+
|
|
187
|
+
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
|
|
188
|
+
console.error('Line fragment shader error:', gl.getShaderInfoLog(fragmentShader));
|
|
189
|
+
this.available = false;
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Link program
|
|
194
|
+
this.program = gl.createProgram();
|
|
195
|
+
gl.attachShader(this.program, vertexShader);
|
|
196
|
+
gl.attachShader(this.program, fragmentShader);
|
|
197
|
+
gl.linkProgram(this.program);
|
|
198
|
+
|
|
199
|
+
if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
|
|
200
|
+
console.error('Line program link error:', gl.getProgramInfoLog(this.program));
|
|
201
|
+
this.available = false;
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
gl.useProgram(this.program);
|
|
206
|
+
|
|
207
|
+
// Get attribute locations
|
|
208
|
+
this.aPosition = gl.getAttribLocation(this.program, 'aPosition');
|
|
209
|
+
this.aColor = gl.getAttribLocation(this.program, 'aColor');
|
|
210
|
+
|
|
211
|
+
// Get uniform locations
|
|
212
|
+
this.uResolution = gl.getUniformLocation(this.program, 'uResolution');
|
|
213
|
+
|
|
214
|
+
// Set initial resolution
|
|
215
|
+
gl.uniform2f(this.uResolution, this.width, this.height);
|
|
216
|
+
|
|
217
|
+
// Clean up shader objects
|
|
218
|
+
gl.deleteShader(vertexShader);
|
|
219
|
+
gl.deleteShader(fragmentShader);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Resize the renderer
|
|
224
|
+
* @param {number} width - New width
|
|
225
|
+
* @param {number} height - New height
|
|
226
|
+
*/
|
|
227
|
+
resize(width, height) {
|
|
228
|
+
this.width = width;
|
|
229
|
+
this.height = height;
|
|
230
|
+
this.canvas.width = width;
|
|
231
|
+
this.canvas.height = height;
|
|
232
|
+
|
|
233
|
+
if (this.available) {
|
|
234
|
+
const gl = this.gl;
|
|
235
|
+
gl.viewport(0, 0, width, height);
|
|
236
|
+
gl.useProgram(this.program);
|
|
237
|
+
gl.uniform2f(this.uResolution, width, height);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Update line segment data in GPU buffers
|
|
243
|
+
*
|
|
244
|
+
* @param {Array} segments - Array of line segments
|
|
245
|
+
* Each segment: { x1, y1, x2, y2, r, g, b, a } or
|
|
246
|
+
* { x1, y1, x2, y2, r1, g1, b1, a1, r2, g2, b2, a2 } for gradient
|
|
247
|
+
* Colors should be 0-255 for RGB, 0-1 for alpha
|
|
248
|
+
* @returns {number} Number of segments updated
|
|
249
|
+
*/
|
|
250
|
+
updateLines(segments) {
|
|
251
|
+
if (!this.available) return 0;
|
|
252
|
+
|
|
253
|
+
const count = Math.min(segments.length, this.maxSegments);
|
|
254
|
+
const gl = this.gl;
|
|
255
|
+
|
|
256
|
+
// Fill typed arrays
|
|
257
|
+
for (let i = 0; i < count; i++) {
|
|
258
|
+
const s = segments[i];
|
|
259
|
+
const pi = i * 4; // position index (2 vertices * 2 coords)
|
|
260
|
+
const ci = i * 8; // color index (2 vertices * 4 components)
|
|
261
|
+
|
|
262
|
+
// Positions
|
|
263
|
+
this._positions[pi] = s.x1;
|
|
264
|
+
this._positions[pi + 1] = s.y1;
|
|
265
|
+
this._positions[pi + 2] = s.x2;
|
|
266
|
+
this._positions[pi + 3] = s.y2;
|
|
267
|
+
|
|
268
|
+
// Colors (check for gradient or single color)
|
|
269
|
+
const hasGradient = s.r1 !== undefined;
|
|
270
|
+
|
|
271
|
+
if (hasGradient) {
|
|
272
|
+
// Gradient: different colors at each end
|
|
273
|
+
const a1 = s.a1 !== undefined ? s.a1 : 1;
|
|
274
|
+
const a2 = s.a2 !== undefined ? s.a2 : 1;
|
|
275
|
+
|
|
276
|
+
// Premultiplied alpha
|
|
277
|
+
this._colors[ci] = (s.r1 / 255) * a1;
|
|
278
|
+
this._colors[ci + 1] = (s.g1 / 255) * a1;
|
|
279
|
+
this._colors[ci + 2] = (s.b1 / 255) * a1;
|
|
280
|
+
this._colors[ci + 3] = a1;
|
|
281
|
+
|
|
282
|
+
this._colors[ci + 4] = (s.r2 / 255) * a2;
|
|
283
|
+
this._colors[ci + 5] = (s.g2 / 255) * a2;
|
|
284
|
+
this._colors[ci + 6] = (s.b2 / 255) * a2;
|
|
285
|
+
this._colors[ci + 7] = a2;
|
|
286
|
+
} else {
|
|
287
|
+
// Single color for both vertices
|
|
288
|
+
const a = s.a !== undefined ? s.a : 1;
|
|
289
|
+
const r = (s.r / 255) * a;
|
|
290
|
+
const g = (s.g / 255) * a;
|
|
291
|
+
const b = (s.b / 255) * a;
|
|
292
|
+
|
|
293
|
+
this._colors[ci] = r;
|
|
294
|
+
this._colors[ci + 1] = g;
|
|
295
|
+
this._colors[ci + 2] = b;
|
|
296
|
+
this._colors[ci + 3] = a;
|
|
297
|
+
|
|
298
|
+
this._colors[ci + 4] = r;
|
|
299
|
+
this._colors[ci + 5] = g;
|
|
300
|
+
this._colors[ci + 6] = b;
|
|
301
|
+
this._colors[ci + 7] = a;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Upload to GPU
|
|
306
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer);
|
|
307
|
+
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._positions.subarray(0, count * 4));
|
|
308
|
+
|
|
309
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer);
|
|
310
|
+
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._colors.subarray(0, count * 8));
|
|
311
|
+
|
|
312
|
+
return count;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Clear the canvas
|
|
317
|
+
* @param {number} r - Red (0-1)
|
|
318
|
+
* @param {number} g - Green (0-1)
|
|
319
|
+
* @param {number} b - Blue (0-1)
|
|
320
|
+
* @param {number} a - Alpha (0-1)
|
|
321
|
+
*/
|
|
322
|
+
clear(r = 0, g = 0, b = 0, a = 0) {
|
|
323
|
+
if (!this.available) return;
|
|
324
|
+
const gl = this.gl;
|
|
325
|
+
gl.clearColor(r, g, b, a);
|
|
326
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Render lines to the WebGL canvas
|
|
331
|
+
* @param {number} count - Number of line segments to render
|
|
332
|
+
*/
|
|
333
|
+
render(count) {
|
|
334
|
+
if (!this.available || count === 0) return;
|
|
335
|
+
|
|
336
|
+
const gl = this.gl;
|
|
337
|
+
|
|
338
|
+
gl.useProgram(this.program);
|
|
339
|
+
|
|
340
|
+
// Bind position attribute
|
|
341
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer);
|
|
342
|
+
gl.enableVertexAttribArray(this.aPosition);
|
|
343
|
+
gl.vertexAttribPointer(this.aPosition, 2, gl.FLOAT, false, 0, 0);
|
|
344
|
+
|
|
345
|
+
// Bind color attribute
|
|
346
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer);
|
|
347
|
+
gl.enableVertexAttribArray(this.aColor);
|
|
348
|
+
gl.vertexAttribPointer(this.aColor, 4, gl.FLOAT, false, 0, 0);
|
|
349
|
+
|
|
350
|
+
// Draw all lines in a single call (2 vertices per segment)
|
|
351
|
+
gl.drawArrays(gl.LINES, 0, count * 2);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Composite the WebGL canvas onto a 2D canvas context
|
|
356
|
+
* @param {CanvasRenderingContext2D} ctx - Target 2D context
|
|
357
|
+
* @param {number} x - X position
|
|
358
|
+
* @param {number} y - Y position
|
|
359
|
+
*/
|
|
360
|
+
compositeOnto(ctx, x = 0, y = 0) {
|
|
361
|
+
if (!this.available) return;
|
|
362
|
+
ctx.drawImage(this.canvas, x, y);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Get the WebGL canvas element
|
|
367
|
+
* @returns {HTMLCanvasElement}
|
|
368
|
+
*/
|
|
369
|
+
getCanvas() {
|
|
370
|
+
return this.canvas;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Destroy the renderer and free resources
|
|
375
|
+
*/
|
|
376
|
+
destroy() {
|
|
377
|
+
if (!this.available) return;
|
|
378
|
+
|
|
379
|
+
const gl = this.gl;
|
|
380
|
+
|
|
381
|
+
if (this.program) {
|
|
382
|
+
gl.deleteProgram(this.program);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
gl.deleteBuffer(this.positionBuffer);
|
|
386
|
+
gl.deleteBuffer(this.colorBuffer);
|
|
387
|
+
|
|
388
|
+
this._positions = null;
|
|
389
|
+
this._colors = null;
|
|
390
|
+
}
|
|
391
|
+
}
|