littlejsengine 1.13.4 → 1.14.4

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 (58) hide show
  1. package/README.md +4 -3
  2. package/dist/littlejs.d.ts +395 -249
  3. package/dist/littlejs.esm.js +1550 -754
  4. package/dist/littlejs.esm.min.js +1 -1
  5. package/dist/littlejs.js +1528 -744
  6. package/dist/littlejs.min.js +1 -1
  7. package/dist/littlejs.release.js +1504 -720
  8. package/examples/box2d/game.js +4 -4
  9. package/examples/box2d/gameObjects.js +1 -1
  10. package/examples/breakout/game.js +30 -25
  11. package/examples/breakoutTutorial/README.md +1 -1
  12. package/examples/breakoutTutorial/game.js +1 -1
  13. package/examples/electron/build.js +1 -1
  14. package/examples/electron/index.html +2 -2
  15. package/examples/empty/game.js +1 -1
  16. package/examples/htmlMenu/index.html +7 -7
  17. package/examples/index.html +208 -97
  18. package/examples/platformer/gameEffects.js +20 -25
  19. package/examples/platformer/gameLevel.js +6 -1
  20. package/examples/shorts/base.html +1 -1
  21. package/examples/shorts/flappyGame.js +2 -1
  22. package/examples/shorts/helloWorld.js +1 -1
  23. package/examples/shorts/music.js +106 -0
  24. package/examples/shorts/parallax.js +71 -0
  25. package/examples/shorts/piano.js +7 -8
  26. package/examples/shorts/postProcess.js +25 -8
  27. package/examples/shorts/shapes.js +12 -18
  28. package/examples/shorts/song.mp3 +0 -0
  29. package/examples/shorts/uiSystem.js +15 -8
  30. package/examples/starter/index.html +2 -2
  31. package/examples/stress/index.html +14 -7
  32. package/examples/style.css +14 -4
  33. package/examples/typescript/build.js +1 -1
  34. package/examples/uiSystem/game.js +11 -11
  35. package/package.json +1 -1
  36. package/plugins/box2d.js +12 -10
  37. package/plugins/drawUtilities.js +5 -5
  38. package/plugins/newgrounds.js +6 -6
  39. package/plugins/pluginExport.js +1 -1
  40. package/plugins/uiSystem.js +103 -79
  41. package/plugins/zzfxm.js +10 -10
  42. package/reference.md +94 -56
  43. package/src/engine.js +28 -24
  44. package/src/engineAudio.js +284 -109
  45. package/src/engineBuild.js +11 -11
  46. package/src/engineDebug.js +29 -29
  47. package/src/engineDraw.js +285 -112
  48. package/src/engineExport.js +28 -16
  49. package/src/engineInput.js +57 -67
  50. package/src/engineMedals.js +25 -25
  51. package/src/engineObject.js +28 -25
  52. package/src/engineParticles.js +59 -59
  53. package/src/engineRelease.js +1 -1
  54. package/src/engineSettings.js +20 -13
  55. package/src/engineTileLayer.js +34 -35
  56. package/src/engineUtilities.js +66 -59
  57. package/src/engineWebGL.js +466 -66
  58. /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 = 5e5;
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()
107
130
  {
108
- if (!glEnable || !glContext) return;
109
-
110
- // set up the shader and canvas
111
- glClearCanvas();
131
+ if (!glPolyMode)
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,87 @@ 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 transform 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 with additive blending off
231
+ glAdditive = glBatchAdditive = false;
232
+
233
+ // force it to enter instanced mode
234
+ glPolyMode = true;
235
+ glSetInstancedMode();
153
236
  }
154
237
 
155
238
  /** Clear the canvas and setup the viewport
@@ -157,21 +240,23 @@ function glPreRender()
157
240
  function glClearCanvas()
158
241
  {
159
242
  if (!glContext) return;
160
-
243
+
161
244
  // clear and set to same size as main canvas
162
- glContext.viewport(0, 0, glCanvas.width=drawCanvas.width, glCanvas.height=drawCanvas.height);
245
+ glCanvas.width = drawCanvas.width;
246
+ glCanvas.height = drawCanvas.height;
247
+ glContext.viewport(0, 0, glCanvas.width, glCanvas.height);
163
248
  glContext.clear(glContext.COLOR_BUFFER_BIT);
164
249
  }
165
250
 
166
- /** Set the WebGl texture, called automatically if using multiple textures
251
+ /** Set the WebGL texture, called automatically if using multiple textures
167
252
  * - This may also flush the gl buffer resulting in more draw calls and worse performance
168
253
  * @param {WebGLTexture} texture
169
- * @param {boolean} wrap - Should the texture wrap or clamp
254
+ * @param {boolean} [wrap] - Should the texture wrap or clamp
170
255
  * @memberof WebGL */
171
256
  function glSetTexture(texture, wrap=false)
172
257
  {
173
258
  // must flush cache with the old texture to set a new one
174
- if (!glContext || texture == glActiveTexture)
259
+ if (!glContext || texture === glActiveTexture)
175
260
  return;
176
261
 
177
262
  glFlush();
@@ -184,8 +269,8 @@ function glSetTexture(texture, wrap=false)
184
269
  }
185
270
 
186
271
  /** Compile WebGL shader of the given type, will throw errors if in debug mode
187
- * @param {String} source
188
- * @param {Number} type
272
+ * @param {string} source
273
+ * @param {number} type
189
274
  * @return {WebGLShader}
190
275
  * @memberof WebGL */
191
276
  function glCompileShader(source, type)
@@ -204,8 +289,8 @@ function glCompileShader(source, type)
204
289
  }
205
290
 
206
291
  /** Create WebGL program with given shaders
207
- * @param {String} vsSource
208
- * @param {String} fsSource
292
+ * @param {string} vsSource
293
+ * @param {string} fsSource
209
294
  * @return {WebGLProgram}
210
295
  * @memberof WebGL */
211
296
  function glCreateProgram(vsSource, fsSource)
@@ -253,7 +338,7 @@ function glCreateTexture(image)
253
338
  const whitePixel = new Uint8Array([255, 255, 255, 255]);
254
339
  glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, 1, 1, 0, glContext.RGBA, glContext.UNSIGNED_BYTE, whitePixel);
255
340
  }
256
-
341
+
257
342
  // set texture filtering
258
343
  const filter = tilesPixelated ? glContext.NEAREST : glContext.LINEAR;
259
344
  glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, filter);
@@ -261,7 +346,6 @@ function glCreateTexture(image)
261
346
  return texture;
262
347
  }
263
348
 
264
-
265
349
  /** Deletes a WebGL texture
266
350
  * @param {WebGLTexture} [texture]
267
351
  * @memberof WebGL */
@@ -289,18 +373,25 @@ function glSetTextureData(texture, image)
289
373
  * @memberof WebGL */
290
374
  function glFlush()
291
375
  {
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;
376
+ if (glEnable && glContext && glBatchCount)
377
+ {
378
+ // set bend mode
379
+ const destBlend = glBatchAdditive ? glContext.ONE : glContext.ONE_MINUS_SRC_ALPHA;
380
+ glContext.blendFuncSeparate(glContext.SRC_ALPHA, destBlend, glContext.ONE, destBlend);
381
+ glContext.enable(glContext.BLEND);
382
+
383
+ const byteLength = glBatchCount *
384
+ (glPolyMode ? gl_INDICES_PER_POLY_VERTEX : gl_INDICES_PER_INSTANCE);
385
+ glContext.bufferSubData(glContext.ARRAY_BUFFER, 0, glPositionData, 0, byteLength);
386
+
387
+ // draw the batch
388
+ if (glPolyMode)
389
+ glContext.drawArrays(glContext.TRIANGLE_STRIP, 0, glBatchCount);
390
+ else
391
+ glContext.drawArraysInstanced(glContext.TRIANGLE_STRIP, 0, 4, glBatchCount);
392
+ drawCount += glBatchCount;
393
+ glBatchCount = 0;
394
+ }
304
395
  glBatchAdditive = glAdditive;
305
396
  }
306
397
 
@@ -316,7 +407,8 @@ function glCopyToContext(context)
316
407
  context.drawImage(glCanvas, 0, 0);
317
408
  }
318
409
 
319
- /** Set anti-aliasing for webgl canvas
410
+ /** Set anti-aliasing for WebGL canvas
411
+ * Must be called before engineInit
320
412
  * @param {boolean} [antialias]
321
413
  * @memberof WebGL */
322
414
  function glSetAntialias(antialias=true)
@@ -326,25 +418,27 @@ function glSetAntialias(antialias=true)
326
418
  }
327
419
 
328
420
  /** 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
421
+ * @param {number} x
422
+ * @param {number} y
423
+ * @param {number} sizeX
424
+ * @param {number} sizeY
425
+ * @param {number} [angle]
426
+ * @param {number} [uv0X]
427
+ * @param {number} [uv0Y]
428
+ * @param {number} [uv1X]
429
+ * @param {number} [uv1Y]
430
+ * @param {number} [rgba=-1] - white is -1
431
+ * @param {number} [rgbaAdditive=0] - black is 0
340
432
  * @memberof WebGL */
341
433
  function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgba=-1, rgbaAdditive=0)
342
434
  {
343
435
  // flush if there is not enough room or if different blend mode
344
- if (glInstanceCount >= gl_MAX_INSTANCES || glBatchAdditive != glAdditive)
436
+ if (glBatchCount >= gl_MAX_INSTANCES || glBatchAdditive !== glAdditive)
345
437
  glFlush();
438
+ glSetInstancedMode();
346
439
 
347
- let offset = glInstanceCount++ * gl_INDICES_PER_INSTANCE;
440
+ glPolyMode = false;
441
+ let offset = glBatchCount++ * gl_INDICES_PER_INSTANCE;
348
442
  glPositionData[offset++] = x;
349
443
  glPositionData[offset++] = y;
350
444
  glPositionData[offset++] = sizeX;
@@ -356,4 +450,310 @@ function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgb
356
450
  glColorData[offset++] = rgba;
357
451
  glColorData[offset++] = rgbaAdditive;
358
452
  glPositionData[offset++] = angle;
453
+ }
454
+
455
+ /** Transform and add a polygon to the gl draw list
456
+ * @param {Array<Vector2>} points - Array of Vector2 points
457
+ * @param {number} rgba - Color of the polygon as a 32-bit integer
458
+ * @param {number} x
459
+ * @param {number} y
460
+ * @param {number} sx
461
+ * @param {number} sy
462
+ * @param {number} angle
463
+ * @param {boolean} [tristrip] - should tristrip algorithm be used
464
+ * @memberof WebGL */
465
+ function glDrawPointsTransform(points, rgba, x, y, sx, sy, angle, tristrip=true)
466
+ {
467
+ const pointsOut = [];
468
+ for (const p of points)
469
+ {
470
+ // transform the point
471
+ const px = p.x*sx;
472
+ const py = p.y*sy;
473
+ const sa = Math.sin(-angle);
474
+ const ca = Math.cos(-angle);
475
+ pointsOut.push(vec2(x + ca*px - sa*py, y + sa*px + ca*py));
476
+ }
477
+ const drawPoints = tristrip ? glPolyStrip(pointsOut) : pointsOut;
478
+ glDrawPoints(drawPoints, rgba);
479
+ }
480
+
481
+ /** Transform and add a polygon to the gl draw list
482
+ * @param {Array<Vector2>} points - Array of Vector2 points
483
+ * @param {number} rgba - Color of the polygon as a 32-bit integer
484
+ * @param {number} lineWidth - Width of the outline
485
+ * @param {number} x
486
+ * @param {number} y
487
+ * @param {number} sx
488
+ * @param {number} sy
489
+ * @param {number} angle
490
+ * @param {boolean} [wrap] - Should the outline connect the first and last points
491
+ * @memberof WebGL */
492
+ function glDrawOutlineTransform(points, rgba, lineWidth, x, y, sx, sy, angle, wrap=true)
493
+ {
494
+ const outlinePoints = glMakeOutline(points, lineWidth, wrap);
495
+ glDrawPointsTransform(outlinePoints, rgba, x, y, sx, sy, angle, false);
496
+ }
497
+
498
+ /** Add a list of points to the gl draw list
499
+ * @param {Array<Vector2>} points - Array of Vector2 points in tri strip order
500
+ * @param {number} rgba - Color as a 32-bit integer
501
+ * @memberof WebGL */
502
+ function glDrawPoints(points, rgba)
503
+ {
504
+ if (!glEnable || points.length < 3)
505
+ return; // needs at least 3 points to have area
506
+
507
+ // flush if there is not enough room or if different blend mode
508
+ const vertCount = points.length + 2;
509
+ if (glBatchCount+vertCount >= gl_MAX_POLY_VERTEXES || glBatchAdditive !== glAdditive)
510
+ glFlush();
511
+ glSetPolyMode();
512
+
513
+ // setup triangle strip with degenerate verts at start and end
514
+ let offset = glBatchCount * gl_INDICES_PER_POLY_VERTEX;
515
+ for(let i = vertCount; i--;)
516
+ {
517
+ const j = clamp(i-1, 0, vertCount-3);
518
+ const point = points[j];
519
+ glPositionData[offset++] = point.x;
520
+ glPositionData[offset++] = point.y;
521
+ glColorData[offset++] = rgba;
522
+ }
523
+ glBatchCount += vertCount;
524
+ }
525
+
526
+ /** Add a list of colored points to the gl draw list
527
+ * @param {Array<Vector2>} points - Array of Vector2 points in tri strip order
528
+ * @param {Array<number>} pointColors - Array of 32-bit integer colors
529
+ * @memberof WebGL */
530
+ function glDrawColoredPoints(points, pointColors)
531
+ {
532
+ if (!glEnable || points.length < 3)
533
+ return; // needs at least 3 points to have area
534
+
535
+ // flush if there is not enough room or if different blend mode
536
+ const vertCount = points.length + 2;
537
+ if (glBatchCount+vertCount >= gl_MAX_POLY_VERTEXES || glBatchAdditive !== glAdditive)
538
+ glFlush();
539
+ glSetPolyMode();
540
+
541
+ // setup triangle strip with degenerate verts at start and end
542
+ let offset = glBatchCount * gl_INDICES_PER_POLY_VERTEX;
543
+ for(let i = vertCount; i--;)
544
+ {
545
+ const j = clamp(i-1, 0, vertCount-3);
546
+ const point = points[j];
547
+ const color = pointColors[j];
548
+ glPositionData[offset++] = point.x;
549
+ glPositionData[offset++] = point.y;
550
+ glColorData[offset++] = color;
551
+ }
552
+ glBatchCount += vertCount;
553
+ }
554
+
555
+ // WebGL internal function to convert polygon to outline triangle strip
556
+ function glMakeOutline(points, width, wrap=true)
557
+ {
558
+ if (points.length < 2)
559
+ return [];
560
+
561
+ const halfWidth = width / 2;
562
+ const strip = [];
563
+ const n = points.length;
564
+ const e = 1e-6;
565
+ const miterLimit = width*100;
566
+ for (let i = 0; i < n; i++)
567
+ {
568
+ // for each vertex, calculate normal based on adjacent edges
569
+ const prev = points[wrap ? (i - 1 + n) % n : max(i - 1, 0)];
570
+ const curr = points[i];
571
+ const next = points[wrap ? (i + 1) % n : min(i + 1, n - 1)];
572
+
573
+ // direction from previous to current
574
+ const dx1 = curr.x - prev.x;
575
+ const dy1 = curr.y - prev.y;
576
+ const len1 = (dx1*dx1 + dy1*dy1)**.5;
577
+
578
+ // direction from current to next
579
+ const dx2 = next.x - curr.x;
580
+ const dy2 = next.y - curr.y;
581
+ const len2 = (dx2*dx2 + dy2*dy2)**.5;
582
+
583
+ if (len1 < e && len2 < e)
584
+ continue; // skip degenerate point
585
+
586
+ // calculate perpendicular normals for each edge
587
+ const nx1 = len1 > e ? -dy1 / len1 : 0;
588
+ const ny1 = len1 > e ? dx1 / len1 : 0;
589
+ const nx2 = len2 > e ? -dy2 / len2 : 0;
590
+ const ny2 = len2 > e ? dx2 / len2 : 0;
591
+
592
+ // average the normals for miter
593
+ let nx = nx1 + nx2;
594
+ let ny = ny1 + ny2;
595
+ const nlen = (nx*nx + ny*ny)**.5;
596
+ if (nlen < e)
597
+ {
598
+ // 180 degree turn - use perpendicular
599
+ nx = nx1;
600
+ ny = ny1;
601
+ }
602
+ else
603
+ {
604
+ // calculate miter length
605
+ nx /= nlen;
606
+ ny /= nlen;
607
+ const dot = nx1 * nx + ny1 * ny;
608
+ if (dot > e)
609
+ {
610
+ // scale normal by miter length, clamped to miterLimit
611
+ const miterLength = min(1 / dot, miterLimit);
612
+ nx *= miterLength;
613
+ ny *= miterLength;
614
+ }
615
+ }
616
+
617
+ // create inner and outer points along the normal
618
+ const inner = vec2(curr.x - nx * halfWidth, curr.y - ny * halfWidth);
619
+ const outer = vec2(curr.x + nx * halfWidth, curr.y + ny * halfWidth);
620
+ strip.push(inner);
621
+ strip.push(outer);
622
+ }
623
+ if (strip.length > 1 && wrap)
624
+ {
625
+ // close the loop
626
+ strip.push(strip[0]);
627
+ strip.push(strip[1]);
628
+ }
629
+ return strip;
630
+ }
631
+
632
+ // WebGL internal function to convert polys to tri strips
633
+ function glPolyStrip(points)
634
+ {
635
+ // validate input
636
+ if (points.length < 3)
637
+ return [];
638
+
639
+ // cross product helper: (b-a) x (c-a)
640
+ const cross = (a,b,c)=> (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
641
+
642
+ // calculate signed area of polygon
643
+ const signedArea = (poly)=>
644
+ {
645
+ let area = 0;
646
+ for (let i = poly.length; i--;)
647
+ {
648
+ const j = (i+1) % poly.length;
649
+ area += poly[i].cross(poly[j]);
650
+ }
651
+ return area;
652
+ }
653
+
654
+ // ensure counter-clockwise winding
655
+ if (signedArea(points) < 0)
656
+ points = points.reverse();
657
+
658
+ // check if point is inside triangle
659
+ const e = 1e-9;
660
+ const pointInTriangle = (p, a, b, c)=>
661
+ {
662
+ const c1 = cross(a, b, p);
663
+ const c2 = cross(b, c, p);
664
+ const c3 = cross(c, a, p);
665
+ const negative = (c1<-e?1:0) + (c2<-e?1:0) + (c3<-e?1:0);
666
+ const positive = (c1> e?1:0) + (c2> e?1:0) + (c3> e?1:0);
667
+ return !(negative && positive);
668
+ };
669
+
670
+ // ear clipping triangulation
671
+ const indices = [];
672
+ for (let i = 0; i < points.length; ++i)
673
+ indices[i] = i;
674
+ const triangles = [];
675
+ let attempts = 0;
676
+ const maxAttempts = points.length ** 2 + 100;
677
+ while (indices.length > 3 && attempts++ < maxAttempts)
678
+ {
679
+ let foundEar = false;
680
+ for (let i = indices.length; --i;)
681
+ {
682
+ const i0 = indices[(i + indices.length - 1) % indices.length];
683
+ const i1 = indices[i];
684
+ const i2 = indices[(i + 1) % indices.length];
685
+ const a = points[i0], b = points[i1], c = points[i2];
686
+
687
+ // check if convex
688
+ if (cross(a, b, c) < e)
689
+ continue;
690
+
691
+ // check if any other point is inside
692
+ let hasInside = false;
693
+ for (let j = 0; j < indices.length; j++)
694
+ {
695
+ const k = indices[j];
696
+ if (k === i0 || k === i1 || k === i2)
697
+ continue;
698
+ const p = points[k];
699
+ hasInside = pointInTriangle(p, a, b, c);
700
+ if (hasInside)
701
+ break;
702
+ }
703
+ if (hasInside)
704
+ continue;
705
+
706
+ // found valid ear
707
+ triangles.push([i0, i1, i2]);
708
+ indices.splice(i, 1);
709
+ foundEar = true;
710
+ break;
711
+ }
712
+
713
+ // fallback for degenerate cases
714
+ if (!foundEar)
715
+ {
716
+ let worstIndex = -1, worstValue = Infinity;
717
+ for (let i = indices.length; --i;)
718
+ {
719
+ const i0 = indices[(i + indices.length - 1) % indices.length];
720
+ const i1 = indices[i];
721
+ const i2 = indices[(i + 1) % indices.length];
722
+ const value = abs(cross(points[i0], points[i1], points[i2]));
723
+ if (value < worstValue)
724
+ {
725
+ worstValue = value;
726
+ worstIndex = i;
727
+ }
728
+ }
729
+ if (worstIndex < 0)
730
+ break;
731
+
732
+ const i0 = indices[(worstIndex + indices.length - 1) % indices.length];
733
+ const i1 = indices[worstIndex];
734
+ const i2 = indices[(worstIndex + 1) % indices.length];
735
+ triangles.push([i0, i1, i2]);
736
+ indices.splice(worstIndex, 1);
737
+ }
738
+ }
739
+
740
+ // add final triangle
741
+ if (indices.length === 3)
742
+ triangles.push([indices[0], indices[1], indices[2]]);
743
+ if (!triangles.length)
744
+ return [];
745
+
746
+ // convert triangles to triangle strip with degenerate connectors
747
+ const strip = [];
748
+ let [a0, b0, c0] = triangles[0];
749
+ strip.push(points[a0], points[b0], points[c0]);
750
+ for (let i = 1; i < triangles.length; i++)
751
+ {
752
+ // add degenerate bridge from last vertex to first of new triangle
753
+ const [a, b, c] = triangles[i];
754
+ strip.push(points[c0], points[a]);
755
+ strip.push(points[a], points[b], points[c]);
756
+ c0 = c;
757
+ }
758
+ return strip;
359
759
  }
File without changes