littlejsengine 1.13.1 → 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 (83) hide show
  1. package/README.md +15 -11
  2. package/copyToGithub.bat +28 -0
  3. package/dist/littlejs.d.ts +430 -304
  4. package/dist/littlejs.esm.js +1745 -982
  5. package/dist/littlejs.esm.min.js +1 -1
  6. package/dist/littlejs.js +1716 -965
  7. package/dist/littlejs.min.js +1 -1
  8. package/dist/littlejs.release.js +1673 -922
  9. package/examples/box2d/game.js +2 -2
  10. package/examples/box2d/gameObjects.js +2 -2
  11. package/examples/box2d/scenes.js +1 -1
  12. package/examples/breakout/game.js +30 -25
  13. package/examples/breakoutTutorial/README.md +44 -41
  14. package/examples/breakoutTutorial/game.js +2 -2
  15. package/examples/electron/build.bat +7 -0
  16. package/examples/electron/build.js +124 -0
  17. package/examples/electron/electron.js +35 -0
  18. package/examples/electron/game.js +140 -0
  19. package/examples/electron/index.html +13 -0
  20. package/examples/electron/package.json +12 -0
  21. package/examples/electron/tiles.png +0 -0
  22. package/examples/empty/game.js +1 -0
  23. package/examples/htmlMenu/game.js +1 -1
  24. package/examples/index.html +332 -160
  25. package/examples/module/game.js +5 -2
  26. package/examples/particles/index.html +12 -3
  27. package/examples/platformer/gameEffects.js +20 -19
  28. package/examples/platformer/gameLevel.js +6 -1
  29. package/examples/shorts/base.html +1 -1
  30. package/examples/shorts/blending.js +9 -5
  31. package/examples/shorts/box2d.js +1 -1
  32. package/examples/shorts/box2dCar.js +1 -1
  33. package/examples/shorts/clock.js +1 -1
  34. package/examples/shorts/colors.js +2 -2
  35. package/examples/shorts/flappyGame.js +2 -1
  36. package/examples/shorts/helloWorld.js +1 -1
  37. package/examples/shorts/maze.js +1 -1
  38. package/examples/shorts/music.js +106 -0
  39. package/examples/shorts/nineSlice.js +9 -9
  40. package/examples/shorts/parallax.js +71 -0
  41. package/examples/shorts/piano.js +9 -10
  42. package/examples/shorts/postProcess.js +25 -8
  43. package/examples/shorts/raycasting.js +2 -2
  44. package/examples/shorts/shapes.js +7 -15
  45. package/examples/shorts/slidingPuzzle.js +2 -2
  46. package/examples/shorts/song.mp3 +0 -0
  47. package/examples/shorts/starfield.js +5 -7
  48. package/examples/shorts/systemFont.js +1 -1
  49. package/examples/shorts/uiSystem.js +16 -8
  50. package/examples/starter/build.js +9 -8
  51. package/examples/starter/game.js +5 -2
  52. package/examples/starter/index.html +2 -2
  53. package/examples/stress/index.html +13 -8
  54. package/examples/style.css +19 -6
  55. package/examples/typescript/build.js +1 -1
  56. package/examples/typescript/game.js +2 -2
  57. package/examples/typescript/game.ts +5 -2
  58. package/examples/uiSystem/game.js +13 -12
  59. package/package.json +2 -2
  60. package/plugins/box2d.js +24 -97
  61. package/plugins/drawUtilities.js +29 -23
  62. package/plugins/newgrounds.js +6 -6
  63. package/plugins/pluginExport.js +1 -1
  64. package/plugins/postProcess.js +7 -0
  65. package/plugins/uiSystem.js +106 -82
  66. package/plugins/zzfxm.js +10 -10
  67. package/reference.md +90 -54
  68. package/src/engine.js +28 -23
  69. package/src/engineAudio.js +285 -110
  70. package/src/engineBuild.js +9 -9
  71. package/src/engineDebug.js +28 -28
  72. package/src/engineDraw.js +346 -202
  73. package/src/engineExport.js +28 -16
  74. package/src/engineInput.js +58 -68
  75. package/src/engineMedals.js +29 -29
  76. package/src/engineObject.js +28 -25
  77. package/src/engineParticles.js +67 -60
  78. package/src/engineRelease.js +1 -1
  79. package/src/engineSettings.js +70 -16
  80. package/src/engineTileLayer.js +34 -34
  81. package/src/engineUtilities.js +69 -62
  82. package/src/engineWebGL.js +467 -65
  83. /package/examples/shorts/{playSound.js → sound.js} +0 -0
@@ -1,12 +1,13 @@
1
1
  /**
2
2
  * LittleJS WebGL Interface
3
- * - All webgl used by the engine is wrapped up here
3
+ * - All WebGL used by the engine is wrapped up here
4
+ * - Will fall back to 2D canvas rendering if WebGL is not supported
4
5
  * - For normal stuff you won't need to see or call anything in this file
5
6
  * - For advanced stuff there are helper functions to create shaders, textures, etc
6
7
  * - Can be disabled with glEnable to revert to 2D canvas rendering
7
8
  * - Batches sprite rendering on GPU for incredibly fast performance
8
9
  * - Sprite transform math is done in the shader where possible
9
- * - Supports shadertoy style post processing shaders
10
+ * - Supports shadertoy style post processing shaders via plugin
10
11
  * @namespace WebGL
11
12
  */
12
13
 
@@ -17,24 +18,27 @@
17
18
  * @memberof WebGL */
18
19
  let glCanvas;
19
20
 
20
- /** 2d context for glCanvas
21
+ /** WebGL2 context for `glCanvas`
21
22
  * @type {WebGL2RenderingContext}
22
23
  * @memberof WebGL */
23
24
  let glContext;
24
25
 
25
- /** 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
26
27
  * @type {boolean}
27
28
  * @memberof WebGL */
28
29
  let glAntialias = true;
29
30
 
30
31
  // WebGL internal variables not exposed to documentation
31
- let glShader, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glInstanceCount, glAdditive, glBatchAdditive;
32
+ let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount;
32
33
 
33
- // WebGL internal constants
34
- const gl_MAX_INSTANCES = 1e4;
34
+ // WebGL internal constants
35
+ const gl_ARRAY_BUFFER_SIZE = 4e5;
35
36
  const gl_INDICES_PER_INSTANCE = 11;
36
37
  const gl_INSTANCE_BYTE_STRIDE = gl_INDICES_PER_INSTANCE * 4;
37
- 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;
38
42
 
39
43
  ///////////////////////////////////////////////////////////////////////////////
40
44
 
@@ -47,11 +51,19 @@ function glInit()
47
51
  glCanvas = document.createElement('canvas');
48
52
  glContext = glCanvas.getContext('webgl2', {antialias:glAntialias});
49
53
 
50
- // create the webgl canvas
54
+ if (!glContext)
55
+ {
56
+ console.warn('WebGL2 not supported, falling back to 2D canvas rendering!');
57
+ glCanvas = glContext = undefined;
58
+ glEnable = false;
59
+ return;
60
+ }
61
+
62
+ // create the WebGL canvas
51
63
  const rootElement = mainCanvas.parentElement;
52
64
  rootElement.appendChild(glCanvas);
53
65
 
54
- // setup vertex and fragment shaders
66
+ // setup instanced rendering shader program
55
67
  glShader = glCreateProgram(
56
68
  '#version 300 es\n' + // specify GLSL ES version
57
69
  'precision highp float;'+ // use highp for better accuracy
@@ -79,40 +91,59 @@ function glInit()
79
91
  '}' // end of shader
80
92
  );
81
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
+
82
116
  // init buffers
83
- const glInstanceData = new ArrayBuffer(gl_INSTANCE_BUFFER_SIZE);
117
+ const glInstanceData = new ArrayBuffer(gl_ARRAY_BUFFER_SIZE);
84
118
  glPositionData = new Float32Array(glInstanceData);
85
119
  glColorData = new Uint32Array(glInstanceData);
86
120
  glArrayBuffer = glContext.createBuffer();
87
121
  glGeometryBuffer = glContext.createBuffer();
88
122
 
89
123
  // create the geometry buffer, triangle strip square
90
- 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]);
91
125
  glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
92
126
  glContext.bufferData(glContext.ARRAY_BUFFER, geometry, glContext.STATIC_DRAW);
93
127
  }
94
128
 
95
- // Setup webgl render each frame, called automatically by engine
96
- // Also used by tile layer rendering when redrawing tiles
97
- function glPreRender()
129
+ function glSetInstancedMode(force=false)
98
130
  {
99
- if (!glEnable || headlessMode) return;
100
-
101
- // set up the shader and canvas
102
- glClearCanvas();
131
+ if (!glPolyMode && !force)
132
+ return;
133
+
134
+ // setup instanced mode
135
+ glFlush();
136
+ glPolyMode = false;
103
137
  glContext.useProgram(glShader);
104
- glContext.activeTexture(glContext.TEXTURE0);
105
- if (textureInfos[0])
106
- glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
107
138
 
108
139
  // set vertex attributes
109
- let offset = glAdditive = glBatchAdditive = 0;
140
+ let offset = 0;
110
141
  const initVertexAttribArray = (name, type, typeSize, size)=>
111
142
  {
112
143
  const location = glContext.getAttribLocation(glShader, name);
113
144
  const stride = typeSize && gl_INSTANCE_BYTE_STRIDE; // only if not geometry
114
145
  const divisor = typeSize && 1; // only if not geometry
115
- const normalize = typeSize == 1; // only if color
146
+ const normalize = typeSize === 1; // only if color
116
147
  glContext.enableVertexAttribArray(location);
117
148
  glContext.vertexAttribPointer(location, size, type, normalize, stride, offset);
118
149
  glContext.vertexAttribDivisor(location, divisor);
@@ -121,46 +152,106 @@ function glPreRender()
121
152
  glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
122
153
  initVertexAttribArray('g', glContext.FLOAT, 0, 2); // geometry
123
154
  glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
124
- 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);
125
156
  initVertexAttribArray('p', glContext.FLOAT, 4, 4); // position & size
126
157
  initVertexAttribArray('u', glContext.FLOAT, 4, 4); // texture coords
127
158
  initVertexAttribArray('c', glContext.UNSIGNED_BYTE, 1, 4); // color
128
159
  initVertexAttribArray('a', glContext.UNSIGNED_BYTE, 1, 4); // additiveColor
129
160
  initVertexAttribArray('r', glContext.FLOAT, 4, 1); // rotation
161
+ }
162
+
163
+ function glSetPolyMode()
164
+ {
165
+ if (glPolyMode)
166
+ return;
130
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
+
131
200
  // build the transform matrix
132
201
  const s = vec2(2*cameraScale).divide(mainCanvasSize);
133
202
  const rotatedCam = cameraPos.rotate(-cameraAngle);
134
203
  const p = vec2(-1).subtract(rotatedCam.multiply(s));
135
204
  const ca = Math.cos(cameraAngle);
136
205
  const sa = Math.sin(cameraAngle);
137
- glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), false,
138
- [
206
+ const transform = [
139
207
  s.x * ca, s.y * sa, 0, 0,
140
208
  -s.x * sa, s.y * ca, 0, 0,
141
209
  1, 1, 1, 0,
142
- p.x, p.y, 0, 1
143
- ]);
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);
144
233
  }
145
234
 
146
235
  /** Clear the canvas and setup the viewport
147
236
  * @memberof WebGL */
148
237
  function glClearCanvas()
149
238
  {
239
+ if (!glContext) return;
240
+
150
241
  // clear and set to same size as main canvas
151
242
  glContext.viewport(0, 0, glCanvas.width=drawCanvas.width, glCanvas.height=drawCanvas.height);
152
243
  glContext.clear(glContext.COLOR_BUFFER_BIT);
153
244
  }
154
245
 
155
- /** Set the WebGl texture, called automatically if using multiple textures
246
+ /** Set the WebGL texture, called automatically if using multiple textures
156
247
  * - This may also flush the gl buffer resulting in more draw calls and worse performance
157
248
  * @param {WebGLTexture} texture
158
- * @param {boolean} wrap - Should the texture wrap or clamp
249
+ * @param {boolean} [wrap] - Should the texture wrap or clamp
159
250
  * @memberof WebGL */
160
251
  function glSetTexture(texture, wrap=false)
161
252
  {
162
253
  // must flush cache with the old texture to set a new one
163
- if (headlessMode || texture == glActiveTexture)
254
+ if (!glContext || texture === glActiveTexture)
164
255
  return;
165
256
 
166
257
  glFlush();
@@ -173,12 +264,14 @@ function glSetTexture(texture, wrap=false)
173
264
  }
174
265
 
175
266
  /** Compile WebGL shader of the given type, will throw errors if in debug mode
176
- * @param {String} source
177
- * @param {Number} type
267
+ * @param {string} source
268
+ * @param {number} type
178
269
  * @return {WebGLShader}
179
270
  * @memberof WebGL */
180
271
  function glCompileShader(source, type)
181
272
  {
273
+ if (!glContext) return;
274
+
182
275
  // build the shader
183
276
  const shader = glContext.createShader(type);
184
277
  glContext.shaderSource(shader, source);
@@ -191,12 +284,14 @@ function glCompileShader(source, type)
191
284
  }
192
285
 
193
286
  /** Create WebGL program with given shaders
194
- * @param {String} vsSource
195
- * @param {String} fsSource
287
+ * @param {string} vsSource
288
+ * @param {string} fsSource
196
289
  * @return {WebGLProgram}
197
290
  * @memberof WebGL */
198
291
  function glCreateProgram(vsSource, fsSource)
199
292
  {
293
+ if (!glContext) return;
294
+
200
295
  // build the program
201
296
  const program = glContext.createProgram();
202
297
  glContext.attachShader(program, glCompileShader(vsSource, glContext.VERTEX_SHADER));
@@ -215,6 +310,8 @@ function glCreateProgram(vsSource, fsSource)
215
310
  * @memberof WebGL */
216
311
  function glCreateTexture(image)
217
312
  {
313
+ if (!glContext) return;
314
+
218
315
  // build the texture
219
316
  const texture = glContext.createTexture();
220
317
  glContext.bindTexture(glContext.TEXTURE_2D, texture);
@@ -236,7 +333,7 @@ function glCreateTexture(image)
236
333
  const whitePixel = new Uint8Array([255, 255, 255, 255]);
237
334
  glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, 1, 1, 0, glContext.RGBA, glContext.UNSIGNED_BYTE, whitePixel);
238
335
  }
239
-
336
+
240
337
  // set texture filtering
241
338
  const filter = tilesPixelated ? glContext.NEAREST : glContext.LINEAR;
242
339
  glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, filter);
@@ -244,12 +341,12 @@ function glCreateTexture(image)
244
341
  return texture;
245
342
  }
246
343
 
247
-
248
344
  /** Deletes a WebGL texture
249
345
  * @param {WebGLTexture} [texture]
250
346
  * @memberof WebGL */
251
347
  function glDeleteTexture(texture)
252
348
  {
349
+ if (!glContext) return;
253
350
  glContext.deleteTexture(texture);
254
351
  }
255
352
 
@@ -259,6 +356,8 @@ function glDeleteTexture(texture)
259
356
  * @memberof WebGL */
260
357
  function glSetTextureData(texture, image)
261
358
  {
359
+ if (!glContext) return;
360
+
262
361
  // build the texture
263
362
  ASSERT(!!image && image.width > 0, 'Invalid image data.');
264
363
  glContext.bindTexture(glContext.TEXTURE_2D, texture);
@@ -269,18 +368,23 @@ function glSetTextureData(texture, image)
269
368
  * @memberof WebGL */
270
369
  function glFlush()
271
370
  {
272
- if (!glEnable || !glInstanceCount) return;
273
-
274
- const destBlend = glBatchAdditive ? glContext.ONE : glContext.ONE_MINUS_SRC_ALPHA;
275
- glContext.blendFuncSeparate(glContext.SRC_ALPHA, destBlend, glContext.ONE, destBlend);
276
- glContext.enable(glContext.BLEND);
277
-
278
- // draw all the sprites in the batch and reset the buffer
279
- glContext.bufferSubData(glContext.ARRAY_BUFFER, 0, glPositionData);
280
- glContext.drawArraysInstanced(glContext.TRIANGLE_STRIP, 0, 4, glInstanceCount);
281
- if (debug || showWatermark)
282
- drawCount += glInstanceCount;
283
- 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
+ }
284
388
  glBatchAdditive = glAdditive;
285
389
  }
286
390
 
@@ -289,14 +393,15 @@ function glFlush()
289
393
  * @memberof WebGL */
290
394
  function glCopyToContext(context)
291
395
  {
292
- if (!glEnable)
396
+ if (!glEnable || !glContext)
293
397
  return;
294
398
 
295
399
  glFlush();
296
400
  context.drawImage(glCanvas, 0, 0);
297
401
  }
298
402
 
299
- /** Set anti-aliasing for webgl canvas
403
+ /** Set anti-aliasing for WebGL canvas
404
+ * Must be called before engineInit
300
405
  * @param {boolean} [antialias]
301
406
  * @memberof WebGL */
302
407
  function glSetAntialias(antialias=true)
@@ -306,25 +411,27 @@ function glSetAntialias(antialias=true)
306
411
  }
307
412
 
308
413
  /** Add a sprite to the gl draw list, used by all gl draw functions
309
- * @param {Number} x
310
- * @param {Number} y
311
- * @param {Number} sizeX
312
- * @param {Number} sizeY
313
- * @param {Number} [angle]
314
- * @param {Number} [uv0X]
315
- * @param {Number} [uv0Y]
316
- * @param {Number} [uv1X]
317
- * @param {Number} [uv1Y]
318
- * @param {Number} [rgba=-1] - white is -1
319
- * @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
320
425
  * @memberof WebGL */
321
426
  function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgba=-1, rgbaAdditive=0)
322
427
  {
323
428
  // flush if there is not enough room or if different blend mode
324
- if (glInstanceCount >= gl_MAX_INSTANCES || glBatchAdditive != glAdditive)
429
+ if (glBatchCount >= gl_MAX_INSTANCES || glBatchAdditive !== glAdditive)
325
430
  glFlush();
431
+ glSetInstancedMode();
326
432
 
327
- let offset = glInstanceCount++ * gl_INDICES_PER_INSTANCE;
433
+ glPolyMode = false;
434
+ let offset = glBatchCount++ * gl_INDICES_PER_INSTANCE;
328
435
  glPositionData[offset++] = x;
329
436
  glPositionData[offset++] = y;
330
437
  glPositionData[offset++] = sizeX;
@@ -336,4 +443,299 @@ function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgb
336
443
  glColorData[offset++] = rgba;
337
444
  glColorData[offset++] = rgbaAdditive;
338
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;
339
741
  }