littlejsengine 1.13.4 → 1.14.2

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 (53) hide show
  1. package/README.md +2 -1
  2. package/copyToGithub.bat +28 -0
  3. package/dist/littlejs.d.ts +346 -240
  4. package/dist/littlejs.esm.js +1370 -723
  5. package/dist/littlejs.esm.min.js +1 -1
  6. package/dist/littlejs.js +1350 -709
  7. package/dist/littlejs.min.js +1 -1
  8. package/dist/littlejs.release.js +1333 -692
  9. package/examples/box2d/game.js +1 -1
  10. package/examples/box2d/gameObjects.js +1 -1
  11. package/examples/breakout/game.js +30 -25
  12. package/examples/electron/index.html +2 -2
  13. package/examples/index.html +207 -96
  14. package/examples/platformer/gameEffects.js +19 -18
  15. package/examples/platformer/gameLevel.js +6 -1
  16. package/examples/shorts/base.html +1 -1
  17. package/examples/shorts/flappyGame.js +2 -1
  18. package/examples/shorts/helloWorld.js +1 -1
  19. package/examples/shorts/music.js +106 -0
  20. package/examples/shorts/parallax.js +71 -0
  21. package/examples/shorts/piano.js +7 -8
  22. package/examples/shorts/postProcess.js +25 -8
  23. package/examples/shorts/shapes.js +1 -9
  24. package/examples/shorts/song.mp3 +0 -0
  25. package/examples/shorts/uiSystem.js +15 -8
  26. package/examples/starter/index.html +2 -2
  27. package/examples/stress/index.html +8 -3
  28. package/examples/style.css +14 -4
  29. package/examples/uiSystem/game.js +11 -11
  30. package/package.json +1 -1
  31. package/plugins/box2d.js +10 -8
  32. package/plugins/drawUtilities.js +5 -5
  33. package/plugins/newgrounds.js +6 -6
  34. package/plugins/pluginExport.js +1 -1
  35. package/plugins/uiSystem.js +103 -79
  36. package/plugins/zzfxm.js +10 -10
  37. package/reference.md +90 -54
  38. package/src/engine.js +22 -22
  39. package/src/engineAudio.js +284 -109
  40. package/src/engineBuild.js +9 -9
  41. package/src/engineDebug.js +26 -26
  42. package/src/engineDraw.js +148 -97
  43. package/src/engineExport.js +22 -16
  44. package/src/engineInput.js +57 -67
  45. package/src/engineMedals.js +25 -25
  46. package/src/engineObject.js +25 -22
  47. package/src/engineParticles.js +59 -59
  48. package/src/engineRelease.js +1 -1
  49. package/src/engineSettings.js +20 -13
  50. package/src/engineTileLayer.js +34 -34
  51. package/src/engineUtilities.js +62 -55
  52. package/src/engineWebGL.js +447 -65
  53. /package/examples/shorts/{playSound.js → sound.js} +0 -0
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * LittleJS WebGL Interface
3
- * - All webgl used by the engine is wrapped up here
4
- * - Will fall back to 2D canvas rendering if webgl is not supported
3
+ * - All WebGL used by the engine is wrapped up here
4
+ * - Will fall back to 2D canvas rendering if WebGL is not supported
5
5
  * - For normal stuff you won't need to see or call anything in this file
6
6
  * - For advanced stuff there are helper functions to create shaders, textures, etc
7
7
  * - Can be disabled with glEnable to revert to 2D canvas rendering
@@ -18,24 +18,27 @@
18
18
  * @memberof WebGL */
19
19
  let glCanvas;
20
20
 
21
- /** 2d context for glCanvas
21
+ /** WebGL2 context for `glCanvas`
22
22
  * @type {WebGL2RenderingContext}
23
23
  * @memberof WebGL */
24
24
  let glContext;
25
25
 
26
- /** Should webgl be setup with anti-aliasing? must be set before calling engineInit
26
+ /** Should WebGL be setup with anti-aliasing? must be set before calling engineInit
27
27
  * @type {boolean}
28
28
  * @memberof WebGL */
29
29
  let glAntialias = true;
30
30
 
31
31
  // WebGL internal variables not exposed to documentation
32
- let glShader, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glInstanceCount, glAdditive, glBatchAdditive;
32
+ let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount;
33
33
 
34
- // WebGL internal constants
35
- const gl_MAX_INSTANCES = 1e4;
34
+ // WebGL internal constants
35
+ const gl_ARRAY_BUFFER_SIZE = 4e5;
36
36
  const gl_INDICES_PER_INSTANCE = 11;
37
37
  const gl_INSTANCE_BYTE_STRIDE = gl_INDICES_PER_INSTANCE * 4;
38
- const gl_INSTANCE_BUFFER_SIZE = gl_MAX_INSTANCES * gl_INSTANCE_BYTE_STRIDE;
38
+ const gl_MAX_INSTANCES = gl_ARRAY_BUFFER_SIZE / gl_INSTANCE_BYTE_STRIDE | 0;
39
+ const gl_INDICES_PER_POLY_VERTEX = 3;
40
+ const gl_POLY_VERTEX_BYTE_STRIDE = gl_INDICES_PER_POLY_VERTEX * 4;
41
+ const gl_MAX_POLY_VERTEXES = gl_ARRAY_BUFFER_SIZE / gl_POLY_VERTEX_BYTE_STRIDE | 0;
39
42
 
40
43
  ///////////////////////////////////////////////////////////////////////////////
41
44
 
@@ -56,11 +59,11 @@ function glInit()
56
59
  return;
57
60
  }
58
61
 
59
- // create the webgl canvas
62
+ // create the WebGL canvas
60
63
  const rootElement = mainCanvas.parentElement;
61
64
  rootElement.appendChild(glCanvas);
62
65
 
63
- // setup vertex and fragment shaders
66
+ // setup instanced rendering shader program
64
67
  glShader = glCreateProgram(
65
68
  '#version 300 es\n' + // specify GLSL ES version
66
69
  'precision highp float;'+ // use highp for better accuracy
@@ -88,40 +91,59 @@ function glInit()
88
91
  '}' // end of shader
89
92
  );
90
93
 
94
+ // setup poly rendering shaders
95
+ glPolyShader = glCreateProgram(
96
+ '#version 300 es\n' + // specify GLSL ES version
97
+ 'precision highp float;'+ // use highp for better accuracy
98
+ 'uniform mat4 m;'+ // transform matrix
99
+ 'in vec2 p;'+ // in: position
100
+ 'in vec4 c;'+ // in: color
101
+ 'out vec4 d;'+ // out: color
102
+ 'void main(){'+ // shader entry point
103
+ 'gl_Position=m*vec4(p,1,1);'+ // transform position
104
+ 'd=c;'+ // pass color to fragment shader
105
+ '}' // end of shader
106
+ ,
107
+ '#version 300 es\n' + // specify GLSL ES version
108
+ 'precision highp float;'+ // use highp for better accuracy
109
+ 'in vec4 d;'+ // in: color
110
+ 'out vec4 c;'+ // out: color
111
+ 'void main(){'+ // shader entry point
112
+ 'c=d;'+ // set color
113
+ '}' // end of shader
114
+ );
115
+
91
116
  // init buffers
92
- const glInstanceData = new ArrayBuffer(gl_INSTANCE_BUFFER_SIZE);
117
+ const glInstanceData = new ArrayBuffer(gl_ARRAY_BUFFER_SIZE);
93
118
  glPositionData = new Float32Array(glInstanceData);
94
119
  glColorData = new Uint32Array(glInstanceData);
95
120
  glArrayBuffer = glContext.createBuffer();
96
121
  glGeometryBuffer = glContext.createBuffer();
97
122
 
98
123
  // create the geometry buffer, triangle strip square
99
- const geometry = new Float32Array([glInstanceCount=0,0,1,0,0,1,1,1]);
124
+ const geometry = new Float32Array([glBatchCount=0,0,1,0,0,1,1,1]);
100
125
  glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
101
126
  glContext.bufferData(glContext.ARRAY_BUFFER, geometry, glContext.STATIC_DRAW);
102
127
  }
103
128
 
104
- // Setup webgl render each frame, called automatically by engine
105
- // Also used by tile layer rendering when redrawing tiles
106
- function glPreRender()
129
+ function glSetInstancedMode(force=false)
107
130
  {
108
- if (!glEnable || !glContext) return;
109
-
110
- // set up the shader and canvas
111
- glClearCanvas();
131
+ if (!glPolyMode && !force)
132
+ return;
133
+
134
+ // setup instanced mode
135
+ glFlush();
136
+ glPolyMode = false;
112
137
  glContext.useProgram(glShader);
113
- glContext.activeTexture(glContext.TEXTURE0);
114
- if (textureInfos[0])
115
- glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
116
138
 
117
139
  // set vertex attributes
118
- let offset = glAdditive = glBatchAdditive = 0;
140
+ let offset = 0;
119
141
  const initVertexAttribArray = (name, type, typeSize, size)=>
120
142
  {
121
143
  const location = glContext.getAttribLocation(glShader, name);
122
144
  const stride = typeSize && gl_INSTANCE_BYTE_STRIDE; // only if not geometry
123
145
  const divisor = typeSize && 1; // only if not geometry
124
- const normalize = typeSize == 1; // only if color
146
+ const normalize = typeSize === 1; // only if color
125
147
  glContext.enableVertexAttribArray(location);
126
148
  glContext.vertexAttribPointer(location, size, type, normalize, stride, offset);
127
149
  glContext.vertexAttribDivisor(location, divisor);
@@ -130,26 +152,84 @@ function glPreRender()
130
152
  glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
131
153
  initVertexAttribArray('g', glContext.FLOAT, 0, 2); // geometry
132
154
  glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
133
- glContext.bufferData(glContext.ARRAY_BUFFER, gl_INSTANCE_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
155
+ glContext.bufferData(glContext.ARRAY_BUFFER, gl_ARRAY_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
134
156
  initVertexAttribArray('p', glContext.FLOAT, 4, 4); // position & size
135
157
  initVertexAttribArray('u', glContext.FLOAT, 4, 4); // texture coords
136
158
  initVertexAttribArray('c', glContext.UNSIGNED_BYTE, 1, 4); // color
137
159
  initVertexAttribArray('a', glContext.UNSIGNED_BYTE, 1, 4); // additiveColor
138
160
  initVertexAttribArray('r', glContext.FLOAT, 4, 1); // rotation
161
+ }
162
+
163
+ function glSetPolyMode()
164
+ {
165
+ if (glPolyMode)
166
+ return;
139
167
 
168
+ // setup poly mode
169
+ glFlush();
170
+ glPolyMode = true;
171
+ glContext.useProgram(glPolyShader);
172
+
173
+ // set vertex attributes
174
+ let offset = 0;
175
+ const initVertexAttribArray = (name, type, typeSize, size)=>
176
+ {
177
+ const location = glContext.getAttribLocation(glPolyShader, name);
178
+ const normalize = typeSize === 1; // only normalize if color
179
+ const stride = gl_POLY_VERTEX_BYTE_STRIDE;
180
+ glContext.enableVertexAttribArray(location);
181
+ glContext.vertexAttribPointer(location, size, type, normalize, stride, offset);
182
+ glContext.vertexAttribDivisor(location, 0);
183
+ offset += size*typeSize;
184
+ }
185
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
186
+ glContext.bufferData(glContext.ARRAY_BUFFER, gl_ARRAY_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
187
+ initVertexAttribArray('p', glContext.FLOAT, 4, 2); // position
188
+ initVertexAttribArray('c', glContext.UNSIGNED_BYTE, 1, 4); // color
189
+ }
190
+
191
+ // Setup WebGL render each frame, called automatically by engine
192
+ // Also used by tile layer rendering when redrawing tiles
193
+ function glPreRender()
194
+ {
195
+ if (!glEnable || !glContext) return;
196
+
197
+ // clear the canvas
198
+ glClearCanvas();
199
+
140
200
  // build the transform matrix
141
201
  const s = vec2(2*cameraScale).divide(mainCanvasSize);
142
202
  const rotatedCam = cameraPos.rotate(-cameraAngle);
143
203
  const p = vec2(-1).subtract(rotatedCam.multiply(s));
144
204
  const ca = Math.cos(cameraAngle);
145
205
  const sa = Math.sin(cameraAngle);
146
- glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), false,
147
- [
206
+ const transform = [
148
207
  s.x * ca, s.y * sa, 0, 0,
149
208
  -s.x * sa, s.y * ca, 0, 0,
150
209
  1, 1, 1, 0,
151
- p.x, p.y, 0, 1
152
- ]);
210
+ p.x, p.y, 0, 1];
211
+
212
+ // set the same matrix for both shaders
213
+ const initUniform = (program, uniform, value) =>
214
+ {
215
+ glContext.useProgram(program);
216
+ const location = glContext.getUniformLocation(program, uniform);
217
+ glContext.uniformMatrix4fv(location, false, value);
218
+ }
219
+ initUniform(glPolyShader, 'm', transform);
220
+ initUniform(glShader, 'm', transform);
221
+
222
+ // set the active texture
223
+ glContext.activeTexture(glContext.TEXTURE0);
224
+ if (textureInfos[0])
225
+ {
226
+ glActiveTexture = textureInfos[0].glTexture;
227
+ glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
228
+ }
229
+
230
+ // start in instanced rendering mode with additive blending off
231
+ glAdditive = glBatchAdditive = glPolyMode = false;
232
+ glSetInstancedMode(true);
153
233
  }
154
234
 
155
235
  /** Clear the canvas and setup the viewport
@@ -157,21 +237,21 @@ function glPreRender()
157
237
  function glClearCanvas()
158
238
  {
159
239
  if (!glContext) return;
160
-
240
+
161
241
  // clear and set to same size as main canvas
162
242
  glContext.viewport(0, 0, glCanvas.width=drawCanvas.width, glCanvas.height=drawCanvas.height);
163
243
  glContext.clear(glContext.COLOR_BUFFER_BIT);
164
244
  }
165
245
 
166
- /** Set the WebGl texture, called automatically if using multiple textures
246
+ /** Set the WebGL texture, called automatically if using multiple textures
167
247
  * - This may also flush the gl buffer resulting in more draw calls and worse performance
168
248
  * @param {WebGLTexture} texture
169
- * @param {boolean} wrap - Should the texture wrap or clamp
249
+ * @param {boolean} [wrap] - Should the texture wrap or clamp
170
250
  * @memberof WebGL */
171
251
  function glSetTexture(texture, wrap=false)
172
252
  {
173
253
  // must flush cache with the old texture to set a new one
174
- if (!glContext || texture == glActiveTexture)
254
+ if (!glContext || texture === glActiveTexture)
175
255
  return;
176
256
 
177
257
  glFlush();
@@ -184,8 +264,8 @@ function glSetTexture(texture, wrap=false)
184
264
  }
185
265
 
186
266
  /** Compile WebGL shader of the given type, will throw errors if in debug mode
187
- * @param {String} source
188
- * @param {Number} type
267
+ * @param {string} source
268
+ * @param {number} type
189
269
  * @return {WebGLShader}
190
270
  * @memberof WebGL */
191
271
  function glCompileShader(source, type)
@@ -204,8 +284,8 @@ function glCompileShader(source, type)
204
284
  }
205
285
 
206
286
  /** Create WebGL program with given shaders
207
- * @param {String} vsSource
208
- * @param {String} fsSource
287
+ * @param {string} vsSource
288
+ * @param {string} fsSource
209
289
  * @return {WebGLProgram}
210
290
  * @memberof WebGL */
211
291
  function glCreateProgram(vsSource, fsSource)
@@ -253,7 +333,7 @@ function glCreateTexture(image)
253
333
  const whitePixel = new Uint8Array([255, 255, 255, 255]);
254
334
  glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, 1, 1, 0, glContext.RGBA, glContext.UNSIGNED_BYTE, whitePixel);
255
335
  }
256
-
336
+
257
337
  // set texture filtering
258
338
  const filter = tilesPixelated ? glContext.NEAREST : glContext.LINEAR;
259
339
  glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, filter);
@@ -261,7 +341,6 @@ function glCreateTexture(image)
261
341
  return texture;
262
342
  }
263
343
 
264
-
265
344
  /** Deletes a WebGL texture
266
345
  * @param {WebGLTexture} [texture]
267
346
  * @memberof WebGL */
@@ -289,18 +368,23 @@ function glSetTextureData(texture, image)
289
368
  * @memberof WebGL */
290
369
  function glFlush()
291
370
  {
292
- if (!glEnable || !glContext || !glInstanceCount) return;
293
-
294
- const destBlend = glBatchAdditive ? glContext.ONE : glContext.ONE_MINUS_SRC_ALPHA;
295
- glContext.blendFuncSeparate(glContext.SRC_ALPHA, destBlend, glContext.ONE, destBlend);
296
- glContext.enable(glContext.BLEND);
297
-
298
- // draw all the sprites in the batch and reset the buffer
299
- glContext.bufferSubData(glContext.ARRAY_BUFFER, 0, glPositionData);
300
- glContext.drawArraysInstanced(glContext.TRIANGLE_STRIP, 0, 4, glInstanceCount);
301
- if (debug || showWatermark)
302
- drawCount += glInstanceCount;
303
- glInstanceCount = 0;
371
+ if (glEnable && glContext && glBatchCount)
372
+ {
373
+ // set bend mode
374
+ const destBlend = glBatchAdditive ? glContext.ONE : glContext.ONE_MINUS_SRC_ALPHA;
375
+ glContext.blendFuncSeparate(glContext.SRC_ALPHA, destBlend, glContext.ONE, destBlend);
376
+ glContext.enable(glContext.BLEND);
377
+ glContext.bufferSubData(glContext.ARRAY_BUFFER, 0, glPositionData);
378
+
379
+ // draw the batch
380
+ if (glPolyMode)
381
+ glContext.drawArrays(glContext.TRIANGLE_STRIP, 0, glBatchCount);
382
+ else
383
+ glContext.drawArraysInstanced(glContext.TRIANGLE_STRIP, 0, 4, glBatchCount);
384
+ if (debug || showWatermark)
385
+ drawCount += glBatchCount;
386
+ glBatchCount = 0;
387
+ }
304
388
  glBatchAdditive = glAdditive;
305
389
  }
306
390
 
@@ -316,7 +400,8 @@ function glCopyToContext(context)
316
400
  context.drawImage(glCanvas, 0, 0);
317
401
  }
318
402
 
319
- /** Set anti-aliasing for webgl canvas
403
+ /** Set anti-aliasing for WebGL canvas
404
+ * Must be called before engineInit
320
405
  * @param {boolean} [antialias]
321
406
  * @memberof WebGL */
322
407
  function glSetAntialias(antialias=true)
@@ -326,25 +411,27 @@ function glSetAntialias(antialias=true)
326
411
  }
327
412
 
328
413
  /** Add a sprite to the gl draw list, used by all gl draw functions
329
- * @param {Number} x
330
- * @param {Number} y
331
- * @param {Number} sizeX
332
- * @param {Number} sizeY
333
- * @param {Number} [angle]
334
- * @param {Number} [uv0X]
335
- * @param {Number} [uv0Y]
336
- * @param {Number} [uv1X]
337
- * @param {Number} [uv1Y]
338
- * @param {Number} [rgba=-1] - white is -1
339
- * @param {Number} [rgbaAdditive=0] - black is 0
414
+ * @param {number} x
415
+ * @param {number} y
416
+ * @param {number} sizeX
417
+ * @param {number} sizeY
418
+ * @param {number} [angle]
419
+ * @param {number} [uv0X]
420
+ * @param {number} [uv0Y]
421
+ * @param {number} [uv1X]
422
+ * @param {number} [uv1Y]
423
+ * @param {number} [rgba=-1] - white is -1
424
+ * @param {number} [rgbaAdditive=0] - black is 0
340
425
  * @memberof WebGL */
341
426
  function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgba=-1, rgbaAdditive=0)
342
427
  {
343
428
  // flush if there is not enough room or if different blend mode
344
- if (glInstanceCount >= gl_MAX_INSTANCES || glBatchAdditive != glAdditive)
429
+ if (glBatchCount >= gl_MAX_INSTANCES || glBatchAdditive !== glAdditive)
345
430
  glFlush();
431
+ glSetInstancedMode();
346
432
 
347
- let offset = glInstanceCount++ * gl_INDICES_PER_INSTANCE;
433
+ glPolyMode = false;
434
+ let offset = glBatchCount++ * gl_INDICES_PER_INSTANCE;
348
435
  glPositionData[offset++] = x;
349
436
  glPositionData[offset++] = y;
350
437
  glPositionData[offset++] = sizeX;
@@ -356,4 +443,299 @@ function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgb
356
443
  glColorData[offset++] = rgba;
357
444
  glColorData[offset++] = rgbaAdditive;
358
445
  glPositionData[offset++] = angle;
446
+ }
447
+
448
+ /** Transform and add a polygon to the gl draw list
449
+ * @param {Array} points - Array of Vector2 points
450
+ * @param {number} rgba - Color of the polygon as a 32-bit integer
451
+ * @param {number} x
452
+ * @param {number} y
453
+ * @param {number} sx
454
+ * @param {number} sy
455
+ * @param {number} angle
456
+ * @param {boolean} [tristrip] - should tristrip algorithm be used
457
+ * @memberof WebGL */
458
+ function glDrawPointsTransform(points, rgba, x, y, sx, sy, angle, tristrip=true)
459
+ {
460
+ const pointsOut = [];
461
+ for (const p of points)
462
+ {
463
+ // transform the point
464
+ const px = p.x*sx;
465
+ const py = p.y*sy;
466
+ const sa = Math.sin(-angle);
467
+ const ca = Math.cos(-angle);
468
+ pointsOut.push(vec2(x + ca*px - sa*py, y + sa*px + ca*py));
469
+ }
470
+ const drawPoints = tristrip ? glPolyStrip(pointsOut) : pointsOut;
471
+ glDrawPoints(drawPoints, rgba);
472
+ }
473
+
474
+ /** Transform and add a polygon to the gl draw list
475
+ * @param {Array} points - Array of Vector2 points
476
+ * @param {number} rgba - Color of the polygon as a 32-bit integer
477
+ * @param {number} lineWidth - Width of the outline
478
+ * @param {number} x
479
+ * @param {number} y
480
+ * @param {number} sx
481
+ * @param {number} sy
482
+ * @param {number} angle
483
+ * @memberof WebGL */
484
+ function glDrawOutlineTransform(points, rgba, lineWidth, x, y, sx, sy, angle)
485
+ {
486
+ const outlinePoints = glMakeOutline(points, lineWidth);
487
+ glDrawPointsTransform(outlinePoints, rgba, x, y, sx, sy, angle, false);
488
+ }
489
+
490
+ /** Add a polygon to the gl draw list
491
+ * @param {Array} points - Array of Vector2 points in triangle strip order
492
+ * @param {number} rgba - Color of the polygon as a 32-bit integer
493
+ * @memberof WebGL */
494
+ function glDrawPoints(points, rgba)
495
+ {
496
+ if (!glEnable || points.length < 3)
497
+ return; // needs at least 3 points to have area
498
+
499
+ // add 2 degenerate verts if batching with existing polys to separate them
500
+ const needsBridge = glPolyMode && glBatchCount > 0;
501
+ const bridgeVerts = needsBridge ? 2 : 0;
502
+ const vertCount = points.length + bridgeVerts;
503
+
504
+ // flush if there is not enough room or if different blend mode
505
+ if (!glPolyMode || glBatchCount+vertCount >= gl_MAX_POLY_VERTEXES || glBatchAdditive !== glAdditive)
506
+ glFlush();
507
+ glSetPolyMode();
508
+
509
+ let offset = glBatchCount * gl_INDICES_PER_POLY_VERTEX;
510
+
511
+ // add degenerate bridge if needed (repeat last vertex of previous poly, then first of new poly)
512
+ if (needsBridge)
513
+ {
514
+ // repeat last vertex from previous batch (it's at offset - 3)
515
+ const prevOffset = offset - 3;
516
+ glPositionData[offset++] = glPositionData[prevOffset];
517
+ glPositionData[offset++] = glPositionData[prevOffset + 1];
518
+ glColorData[offset++] = glColorData[prevOffset + 2];
519
+
520
+ // repeat first vertex of new poly
521
+ glPositionData[offset++] = points[0].x;
522
+ glPositionData[offset++] = points[0].y;
523
+ glColorData[offset++] = rgba;
524
+ }
525
+
526
+ // write vertices - they're already in triangle strip order
527
+ for (const point of points)
528
+ {
529
+ glPositionData[offset++] = point.x;
530
+ glPositionData[offset++] = point.y;
531
+ glColorData[offset++] = rgba;
532
+ }
533
+ glBatchCount += vertCount;
534
+ }
535
+
536
+ // WebGL internal function to convert polygon to outline triangle strip
537
+ function glMakeOutline(points, width)
538
+ {
539
+ if (points.length < 2)
540
+ return [];
541
+
542
+ const halfWidth = width / 2;
543
+ const strip = [];
544
+ const n = points.length;
545
+ const e = 1e-6;
546
+ for (let i = 0; i < n; i++)
547
+ {
548
+ // for each vertex, calculate normal based on adjacent edges
549
+ const prev = points[(i - 1 + n) % n];
550
+ const curr = points[i];
551
+ const next = points[(i + 1) % n];
552
+
553
+ // direction from previous to current
554
+ const dx1 = curr.x - prev.x;
555
+ const dy1 = curr.y - prev.y;
556
+ const len1 = (dx1*dx1 + dy1*dy1)**.5;
557
+
558
+ // direction from current to next
559
+ const dx2 = next.x - curr.x;
560
+ const dy2 = next.y - curr.y;
561
+ const len2 = (dx2*dx2 + dy2*dy2)**.5;
562
+
563
+ if (len1 < e && len2 < e)
564
+ continue; // skip degenerate point
565
+
566
+ // calculate perpendicular normals for each edge
567
+ const nx1 = len1 > e ? -dy1 / len1 : 0;
568
+ const ny1 = len1 > e ? dx1 / len1 : 0;
569
+ const nx2 = len2 > e ? -dy2 / len2 : 0;
570
+ const ny2 = len2 > e ? dx2 / len2 : 0;
571
+
572
+ // average the normals for miter
573
+ let nx = nx1 + nx2;
574
+ let ny = ny1 + ny2;
575
+ const nlen = (nx*nx + ny*ny)**.5;
576
+ if (nlen < e)
577
+ {
578
+ // 180 degree turn - use perpendicular
579
+ nx = nx1;
580
+ ny = ny1;
581
+ }
582
+ else
583
+ {
584
+ // calculate miter length
585
+ nx /= nlen;
586
+ ny /= nlen;
587
+ const dot = nx1 * nx + ny1 * ny;
588
+ if (dot > e)
589
+ {
590
+ // scale normal by miter length
591
+ const miterLength = 1 / dot;
592
+ nx *= miterLength;
593
+ ny *= miterLength;
594
+ }
595
+ }
596
+
597
+ // create inner and outer points along the normal
598
+ const inner = vec2(curr.x - nx * halfWidth, curr.y - ny * halfWidth);
599
+ const outer = vec2(curr.x + nx * halfWidth, curr.y + ny * halfWidth);
600
+ strip.push(inner);
601
+ strip.push(outer);
602
+ }
603
+ if (strip.length > 1)
604
+ {
605
+ // close the loop
606
+ strip.push(strip[0]);
607
+ strip.push(strip[1]);
608
+ }
609
+ return strip;
610
+ }
611
+
612
+ // WebGL internal function to convert polys to tri strips
613
+ function glPolyStrip(points)
614
+ {
615
+ // validate input
616
+ if (points.length < 3)
617
+ return [];
618
+
619
+ // cross product helper: (b-a) x (c-a)
620
+ const cross = (a,b,c)=> (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
621
+
622
+ // calculate signed area of polygon
623
+ const signedArea = (poly)=>
624
+ {
625
+ let area = 0;
626
+ for (let i = poly.length; i--;)
627
+ {
628
+ const j = (i+1) % poly.length;
629
+ area += poly[i].cross(poly[j]);
630
+ }
631
+ return area;
632
+ }
633
+
634
+ // ensure counter-clockwise winding
635
+ if (signedArea(points) < 0)
636
+ points = points.reverse();
637
+
638
+ // tolerance constants
639
+ const e = 1e-10;
640
+
641
+ // check if point is inside triangle
642
+ const pointInTriangle = (p, a, b, c)=>
643
+ {
644
+ const c1 = cross(a, b, p);
645
+ const c2 = cross(b, c, p);
646
+ const c3 = cross(c, a, p);
647
+ const negative = (c1<-e?1:0) + (c2<-e?1:0) + (c3<-e?1:0);
648
+ const positive = (c1> e?1:0) + (c2> e?1:0) + (c3> e?1:0);
649
+ return !(negative && positive);
650
+ };
651
+
652
+ // ear clipping triangulation
653
+ const indices = [];
654
+ for (let i = 0; i < points.length; ++i)
655
+ indices[i] = i;
656
+ const triangles = [];
657
+ let attempts = 0;
658
+ const maxAttempts = points.length ** 2 + 100;
659
+ while (indices.length > 3 && attempts++ < maxAttempts)
660
+ {
661
+ let foundEar = false;
662
+ for (let i = indices.length; --i;)
663
+ {
664
+ const i0 = indices[(i + indices.length - 1) % indices.length];
665
+ const i1 = indices[i];
666
+ const i2 = indices[(i + 1) % indices.length];
667
+ const a = points[i0], b = points[i1], c = points[i2];
668
+
669
+ // check if convex
670
+ if (cross(a, b, c) < e)
671
+ continue;
672
+
673
+ // check if any other point is inside
674
+ let hasInside = false;
675
+ for (let j = 0; j < indices.length; j++)
676
+ {
677
+ const k = indices[j];
678
+ if (k === i0 || k === i1 || k === i2)
679
+ continue;
680
+ const p = points[k];
681
+ hasInside = pointInTriangle(p, a, b, c);
682
+ if (hasInside)
683
+ break;
684
+ }
685
+ if (hasInside)
686
+ continue;
687
+
688
+ // found valid ear
689
+ triangles.push([i0, i1, i2]);
690
+ indices.splice(i, 1);
691
+ foundEar = true;
692
+ break;
693
+ }
694
+
695
+ // fallback for degenerate cases
696
+ if (!foundEar)
697
+ {
698
+ let worstIndex = -1, worstValue = Infinity;
699
+ for (let i = indices.length; --i;)
700
+ {
701
+ const i0 = indices[(i + indices.length - 1) % indices.length];
702
+ const i1 = indices[i];
703
+ const i2 = indices[(i + 1) % indices.length];
704
+ const value = abs(cross(points[i0], points[i1], points[i2]));
705
+ if (value < worstValue)
706
+ {
707
+ worstValue = value;
708
+ worstIndex = i;
709
+ }
710
+ }
711
+ if (worstIndex < 0)
712
+ break;
713
+
714
+ const i0 = indices[(worstIndex + indices.length - 1) % indices.length];
715
+ const i1 = indices[worstIndex];
716
+ const i2 = indices[(worstIndex + 1) % indices.length];
717
+ triangles.push([i0, i1, i2]);
718
+ indices.splice(worstIndex, 1);
719
+ }
720
+ }
721
+
722
+ // add final triangle
723
+ if (indices.length === 3)
724
+ triangles.push([indices[0], indices[1], indices[2]]);
725
+ if (!triangles.length)
726
+ return [];
727
+
728
+ // convert triangles to triangle strip with degenerate connectors
729
+ const strip = [];
730
+ let [a0, b0, c0] = triangles[0];
731
+ strip.push(points[a0], points[b0], points[c0]);
732
+ for (let i = 1; i < triangles.length; i++)
733
+ {
734
+ // add degenerate bridge from last vertex to first of new triangle
735
+ const [a, b, c] = triangles[i];
736
+ strip.push(points[c0], points[a]);
737
+ strip.push(points[a], points[b], points[c]);
738
+ c0 = c;
739
+ }
740
+ return strip;
359
741
  }
File without changes