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.
- package/README.md +4 -3
- package/dist/littlejs.d.ts +395 -249
- package/dist/littlejs.esm.js +1550 -754
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +1528 -744
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +1504 -720
- package/examples/box2d/game.js +4 -4
- package/examples/box2d/gameObjects.js +1 -1
- package/examples/breakout/game.js +30 -25
- package/examples/breakoutTutorial/README.md +1 -1
- package/examples/breakoutTutorial/game.js +1 -1
- package/examples/electron/build.js +1 -1
- package/examples/electron/index.html +2 -2
- package/examples/empty/game.js +1 -1
- package/examples/htmlMenu/index.html +7 -7
- package/examples/index.html +208 -97
- package/examples/platformer/gameEffects.js +20 -25
- package/examples/platformer/gameLevel.js +6 -1
- package/examples/shorts/base.html +1 -1
- package/examples/shorts/flappyGame.js +2 -1
- package/examples/shorts/helloWorld.js +1 -1
- package/examples/shorts/music.js +106 -0
- package/examples/shorts/parallax.js +71 -0
- package/examples/shorts/piano.js +7 -8
- package/examples/shorts/postProcess.js +25 -8
- package/examples/shorts/shapes.js +12 -18
- package/examples/shorts/song.mp3 +0 -0
- package/examples/shorts/uiSystem.js +15 -8
- package/examples/starter/index.html +2 -2
- package/examples/stress/index.html +14 -7
- package/examples/style.css +14 -4
- package/examples/typescript/build.js +1 -1
- package/examples/uiSystem/game.js +11 -11
- package/package.json +1 -1
- package/plugins/box2d.js +12 -10
- package/plugins/drawUtilities.js +5 -5
- package/plugins/newgrounds.js +6 -6
- package/plugins/pluginExport.js +1 -1
- package/plugins/uiSystem.js +103 -79
- package/plugins/zzfxm.js +10 -10
- package/reference.md +94 -56
- package/src/engine.js +28 -24
- package/src/engineAudio.js +284 -109
- package/src/engineBuild.js +11 -11
- package/src/engineDebug.js +29 -29
- package/src/engineDraw.js +285 -112
- package/src/engineExport.js +28 -16
- package/src/engineInput.js +57 -67
- package/src/engineMedals.js +25 -25
- package/src/engineObject.js +28 -25
- package/src/engineParticles.js +59 -59
- package/src/engineRelease.js +1 -1
- package/src/engineSettings.js +20 -13
- package/src/engineTileLayer.js +34 -35
- package/src/engineUtilities.js +66 -59
- package/src/engineWebGL.js +466 -66
- /package/examples/shorts/{playSound.js → sound.js} +0 -0
package/src/engineWebGL.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* LittleJS WebGL Interface
|
|
3
|
-
* - All
|
|
4
|
-
* - Will fall back to 2D canvas rendering if
|
|
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
|
-
/**
|
|
21
|
+
/** WebGL2 context for `glCanvas`
|
|
22
22
|
* @type {WebGL2RenderingContext}
|
|
23
23
|
* @memberof WebGL */
|
|
24
24
|
let glContext;
|
|
25
25
|
|
|
26
|
-
/** Should
|
|
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,
|
|
32
|
+
let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount;
|
|
33
33
|
|
|
34
|
-
// WebGL internal constants
|
|
35
|
-
const
|
|
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
|
|
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
|
|
62
|
+
// create the WebGL canvas
|
|
60
63
|
const rootElement = mainCanvas.parentElement;
|
|
61
64
|
rootElement.appendChild(glCanvas);
|
|
62
65
|
|
|
63
|
-
// setup
|
|
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(
|
|
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([
|
|
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
|
-
|
|
105
|
-
// Also used by tile layer rendering when redrawing tiles
|
|
106
|
-
function glPreRender()
|
|
129
|
+
function glSetInstancedMode()
|
|
107
130
|
{
|
|
108
|
-
if (!
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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 =
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 {
|
|
188
|
-
* @param {
|
|
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 {
|
|
208
|
-
* @param {
|
|
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 (
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
|
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 {
|
|
330
|
-
* @param {
|
|
331
|
-
* @param {
|
|
332
|
-
* @param {
|
|
333
|
-
* @param {
|
|
334
|
-
* @param {
|
|
335
|
-
* @param {
|
|
336
|
-
* @param {
|
|
337
|
-
* @param {
|
|
338
|
-
* @param {
|
|
339
|
-
* @param {
|
|
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 (
|
|
436
|
+
if (glBatchCount >= gl_MAX_INSTANCES || glBatchAdditive !== glAdditive)
|
|
345
437
|
glFlush();
|
|
438
|
+
glSetInstancedMode();
|
|
346
439
|
|
|
347
|
-
|
|
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
|