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.
- package/README.md +15 -11
- package/copyToGithub.bat +28 -0
- package/dist/littlejs.d.ts +430 -304
- package/dist/littlejs.esm.js +1745 -982
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +1716 -965
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +1673 -922
- package/examples/box2d/game.js +2 -2
- package/examples/box2d/gameObjects.js +2 -2
- package/examples/box2d/scenes.js +1 -1
- package/examples/breakout/game.js +30 -25
- package/examples/breakoutTutorial/README.md +44 -41
- package/examples/breakoutTutorial/game.js +2 -2
- package/examples/electron/build.bat +7 -0
- package/examples/electron/build.js +124 -0
- package/examples/electron/electron.js +35 -0
- package/examples/electron/game.js +140 -0
- package/examples/electron/index.html +13 -0
- package/examples/electron/package.json +12 -0
- package/examples/electron/tiles.png +0 -0
- package/examples/empty/game.js +1 -0
- package/examples/htmlMenu/game.js +1 -1
- package/examples/index.html +332 -160
- package/examples/module/game.js +5 -2
- package/examples/particles/index.html +12 -3
- package/examples/platformer/gameEffects.js +20 -19
- package/examples/platformer/gameLevel.js +6 -1
- package/examples/shorts/base.html +1 -1
- package/examples/shorts/blending.js +9 -5
- package/examples/shorts/box2d.js +1 -1
- package/examples/shorts/box2dCar.js +1 -1
- package/examples/shorts/clock.js +1 -1
- package/examples/shorts/colors.js +2 -2
- package/examples/shorts/flappyGame.js +2 -1
- package/examples/shorts/helloWorld.js +1 -1
- package/examples/shorts/maze.js +1 -1
- package/examples/shorts/music.js +106 -0
- package/examples/shorts/nineSlice.js +9 -9
- package/examples/shorts/parallax.js +71 -0
- package/examples/shorts/piano.js +9 -10
- package/examples/shorts/postProcess.js +25 -8
- package/examples/shorts/raycasting.js +2 -2
- package/examples/shorts/shapes.js +7 -15
- package/examples/shorts/slidingPuzzle.js +2 -2
- package/examples/shorts/song.mp3 +0 -0
- package/examples/shorts/starfield.js +5 -7
- package/examples/shorts/systemFont.js +1 -1
- package/examples/shorts/uiSystem.js +16 -8
- package/examples/starter/build.js +9 -8
- package/examples/starter/game.js +5 -2
- package/examples/starter/index.html +2 -2
- package/examples/stress/index.html +13 -8
- package/examples/style.css +19 -6
- package/examples/typescript/build.js +1 -1
- package/examples/typescript/game.js +2 -2
- package/examples/typescript/game.ts +5 -2
- package/examples/uiSystem/game.js +13 -12
- package/package.json +2 -2
- package/plugins/box2d.js +24 -97
- package/plugins/drawUtilities.js +29 -23
- package/plugins/newgrounds.js +6 -6
- package/plugins/pluginExport.js +1 -1
- package/plugins/postProcess.js +7 -0
- package/plugins/uiSystem.js +106 -82
- package/plugins/zzfxm.js +10 -10
- package/reference.md +90 -54
- package/src/engine.js +28 -23
- package/src/engineAudio.js +285 -110
- package/src/engineBuild.js +9 -9
- package/src/engineDebug.js +28 -28
- package/src/engineDraw.js +346 -202
- package/src/engineExport.js +28 -16
- package/src/engineInput.js +58 -68
- package/src/engineMedals.js +29 -29
- package/src/engineObject.js +28 -25
- package/src/engineParticles.js +67 -60
- package/src/engineRelease.js +1 -1
- package/src/engineSettings.js +70 -16
- package/src/engineTileLayer.js +34 -34
- package/src/engineUtilities.js +69 -62
- package/src/engineWebGL.js +467 -65
- /package/examples/shorts/{playSound.js → sound.js} +0 -0
package/src/engineWebGL.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* LittleJS WebGL Interface
|
|
3
|
-
* - All
|
|
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
|
-
/**
|
|
21
|
+
/** WebGL2 context for `glCanvas`
|
|
21
22
|
* @type {WebGL2RenderingContext}
|
|
22
23
|
* @memberof WebGL */
|
|
23
24
|
let glContext;
|
|
24
25
|
|
|
25
|
-
/** Should
|
|
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,
|
|
32
|
+
let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount;
|
|
32
33
|
|
|
33
|
-
// WebGL internal constants
|
|
34
|
-
const
|
|
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
|
|
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
|
-
|
|
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
|
|
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(
|
|
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([
|
|
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
|
-
|
|
96
|
-
// Also used by tile layer rendering when redrawing tiles
|
|
97
|
-
function glPreRender()
|
|
129
|
+
function glSetInstancedMode(force=false)
|
|
98
130
|
{
|
|
99
|
-
if (!
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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 =
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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 {
|
|
177
|
-
* @param {
|
|
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 {
|
|
195
|
-
* @param {
|
|
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 (
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
|
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 {
|
|
310
|
-
* @param {
|
|
311
|
-
* @param {
|
|
312
|
-
* @param {
|
|
313
|
-
* @param {
|
|
314
|
-
* @param {
|
|
315
|
-
* @param {
|
|
316
|
-
* @param {
|
|
317
|
-
* @param {
|
|
318
|
-
* @param {
|
|
319
|
-
* @param {
|
|
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 (
|
|
429
|
+
if (glBatchCount >= gl_MAX_INSTANCES || glBatchAdditive !== glAdditive)
|
|
325
430
|
glFlush();
|
|
431
|
+
glSetInstancedMode();
|
|
326
432
|
|
|
327
|
-
|
|
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
|
}
|