littlejsengine 1.13.4 → 1.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/copyToGithub.bat +28 -0
- package/dist/littlejs.d.ts +346 -240
- package/dist/littlejs.esm.js +1370 -723
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +1350 -709
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +1333 -692
- package/examples/box2d/game.js +1 -1
- package/examples/box2d/gameObjects.js +1 -1
- package/examples/breakout/game.js +30 -25
- package/examples/electron/index.html +2 -2
- package/examples/index.html +207 -96
- package/examples/platformer/gameEffects.js +19 -18
- 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 +1 -9
- 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 +8 -3
- package/examples/style.css +14 -4
- package/examples/uiSystem/game.js +11 -11
- package/package.json +1 -1
- package/plugins/box2d.js +10 -8
- 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 +90 -54
- package/src/engine.js +22 -22
- package/src/engineAudio.js +284 -109
- package/src/engineBuild.js +9 -9
- package/src/engineDebug.js +26 -26
- package/src/engineDraw.js +148 -97
- package/src/engineExport.js +22 -16
- package/src/engineInput.js +57 -67
- package/src/engineMedals.js +25 -25
- package/src/engineObject.js +25 -22
- package/src/engineParticles.js +59 -59
- package/src/engineRelease.js +1 -1
- package/src/engineSettings.js +20 -13
- package/src/engineTileLayer.js +34 -34
- package/src/engineUtilities.js +62 -55
- package/src/engineWebGL.js +447 -65
- /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 = 4e5;
|
|
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(force=false)
|
|
107
130
|
{
|
|
108
|
-
if (!
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
131
|
+
if (!glPolyMode && !force)
|
|
132
|
+
return;
|
|
133
|
+
|
|
134
|
+
// setup instanced mode
|
|
135
|
+
glFlush();
|
|
136
|
+
glPolyMode = false;
|
|
112
137
|
glContext.useProgram(glShader);
|
|
113
|
-
glContext.activeTexture(glContext.TEXTURE0);
|
|
114
|
-
if (textureInfos[0])
|
|
115
|
-
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
|
|
116
138
|
|
|
117
139
|
// set vertex attributes
|
|
118
|
-
let offset =
|
|
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,84 @@ function glPreRender()
|
|
|
130
152
|
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
131
153
|
initVertexAttribArray('g', glContext.FLOAT, 0, 2); // geometry
|
|
132
154
|
glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
|
|
133
|
-
glContext.bufferData(glContext.ARRAY_BUFFER,
|
|
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 matrix for both shaders
|
|
213
|
+
const initUniform = (program, uniform, value) =>
|
|
214
|
+
{
|
|
215
|
+
glContext.useProgram(program);
|
|
216
|
+
const location = glContext.getUniformLocation(program, uniform);
|
|
217
|
+
glContext.uniformMatrix4fv(location, false, value);
|
|
218
|
+
}
|
|
219
|
+
initUniform(glPolyShader, 'm', transform);
|
|
220
|
+
initUniform(glShader, 'm', transform);
|
|
221
|
+
|
|
222
|
+
// set the active texture
|
|
223
|
+
glContext.activeTexture(glContext.TEXTURE0);
|
|
224
|
+
if (textureInfos[0])
|
|
225
|
+
{
|
|
226
|
+
glActiveTexture = textureInfos[0].glTexture;
|
|
227
|
+
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// start in instanced rendering mode with additive blending off
|
|
231
|
+
glAdditive = glBatchAdditive = glPolyMode = false;
|
|
232
|
+
glSetInstancedMode(true);
|
|
153
233
|
}
|
|
154
234
|
|
|
155
235
|
/** Clear the canvas and setup the viewport
|
|
@@ -157,21 +237,21 @@ function glPreRender()
|
|
|
157
237
|
function glClearCanvas()
|
|
158
238
|
{
|
|
159
239
|
if (!glContext) return;
|
|
160
|
-
|
|
240
|
+
|
|
161
241
|
// clear and set to same size as main canvas
|
|
162
242
|
glContext.viewport(0, 0, glCanvas.width=drawCanvas.width, glCanvas.height=drawCanvas.height);
|
|
163
243
|
glContext.clear(glContext.COLOR_BUFFER_BIT);
|
|
164
244
|
}
|
|
165
245
|
|
|
166
|
-
/** Set the
|
|
246
|
+
/** Set the WebGL texture, called automatically if using multiple textures
|
|
167
247
|
* - This may also flush the gl buffer resulting in more draw calls and worse performance
|
|
168
248
|
* @param {WebGLTexture} texture
|
|
169
|
-
* @param {boolean} wrap - Should the texture wrap or clamp
|
|
249
|
+
* @param {boolean} [wrap] - Should the texture wrap or clamp
|
|
170
250
|
* @memberof WebGL */
|
|
171
251
|
function glSetTexture(texture, wrap=false)
|
|
172
252
|
{
|
|
173
253
|
// must flush cache with the old texture to set a new one
|
|
174
|
-
if (!glContext || texture
|
|
254
|
+
if (!glContext || texture === glActiveTexture)
|
|
175
255
|
return;
|
|
176
256
|
|
|
177
257
|
glFlush();
|
|
@@ -184,8 +264,8 @@ function glSetTexture(texture, wrap=false)
|
|
|
184
264
|
}
|
|
185
265
|
|
|
186
266
|
/** Compile WebGL shader of the given type, will throw errors if in debug mode
|
|
187
|
-
* @param {
|
|
188
|
-
* @param {
|
|
267
|
+
* @param {string} source
|
|
268
|
+
* @param {number} type
|
|
189
269
|
* @return {WebGLShader}
|
|
190
270
|
* @memberof WebGL */
|
|
191
271
|
function glCompileShader(source, type)
|
|
@@ -204,8 +284,8 @@ function glCompileShader(source, type)
|
|
|
204
284
|
}
|
|
205
285
|
|
|
206
286
|
/** Create WebGL program with given shaders
|
|
207
|
-
* @param {
|
|
208
|
-
* @param {
|
|
287
|
+
* @param {string} vsSource
|
|
288
|
+
* @param {string} fsSource
|
|
209
289
|
* @return {WebGLProgram}
|
|
210
290
|
* @memberof WebGL */
|
|
211
291
|
function glCreateProgram(vsSource, fsSource)
|
|
@@ -253,7 +333,7 @@ function glCreateTexture(image)
|
|
|
253
333
|
const whitePixel = new Uint8Array([255, 255, 255, 255]);
|
|
254
334
|
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, 1, 1, 0, glContext.RGBA, glContext.UNSIGNED_BYTE, whitePixel);
|
|
255
335
|
}
|
|
256
|
-
|
|
336
|
+
|
|
257
337
|
// set texture filtering
|
|
258
338
|
const filter = tilesPixelated ? glContext.NEAREST : glContext.LINEAR;
|
|
259
339
|
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, filter);
|
|
@@ -261,7 +341,6 @@ function glCreateTexture(image)
|
|
|
261
341
|
return texture;
|
|
262
342
|
}
|
|
263
343
|
|
|
264
|
-
|
|
265
344
|
/** Deletes a WebGL texture
|
|
266
345
|
* @param {WebGLTexture} [texture]
|
|
267
346
|
* @memberof WebGL */
|
|
@@ -289,18 +368,23 @@ function glSetTextureData(texture, image)
|
|
|
289
368
|
* @memberof WebGL */
|
|
290
369
|
function glFlush()
|
|
291
370
|
{
|
|
292
|
-
if (
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
371
|
+
if (glEnable && glContext && glBatchCount)
|
|
372
|
+
{
|
|
373
|
+
// set bend mode
|
|
374
|
+
const destBlend = glBatchAdditive ? glContext.ONE : glContext.ONE_MINUS_SRC_ALPHA;
|
|
375
|
+
glContext.blendFuncSeparate(glContext.SRC_ALPHA, destBlend, glContext.ONE, destBlend);
|
|
376
|
+
glContext.enable(glContext.BLEND);
|
|
377
|
+
glContext.bufferSubData(glContext.ARRAY_BUFFER, 0, glPositionData);
|
|
378
|
+
|
|
379
|
+
// draw the batch
|
|
380
|
+
if (glPolyMode)
|
|
381
|
+
glContext.drawArrays(glContext.TRIANGLE_STRIP, 0, glBatchCount);
|
|
382
|
+
else
|
|
383
|
+
glContext.drawArraysInstanced(glContext.TRIANGLE_STRIP, 0, 4, glBatchCount);
|
|
384
|
+
if (debug || showWatermark)
|
|
385
|
+
drawCount += glBatchCount;
|
|
386
|
+
glBatchCount = 0;
|
|
387
|
+
}
|
|
304
388
|
glBatchAdditive = glAdditive;
|
|
305
389
|
}
|
|
306
390
|
|
|
@@ -316,7 +400,8 @@ function glCopyToContext(context)
|
|
|
316
400
|
context.drawImage(glCanvas, 0, 0);
|
|
317
401
|
}
|
|
318
402
|
|
|
319
|
-
/** Set anti-aliasing for
|
|
403
|
+
/** Set anti-aliasing for WebGL canvas
|
|
404
|
+
* Must be called before engineInit
|
|
320
405
|
* @param {boolean} [antialias]
|
|
321
406
|
* @memberof WebGL */
|
|
322
407
|
function glSetAntialias(antialias=true)
|
|
@@ -326,25 +411,27 @@ function glSetAntialias(antialias=true)
|
|
|
326
411
|
}
|
|
327
412
|
|
|
328
413
|
/** Add a sprite to the gl draw list, used by all gl draw functions
|
|
329
|
-
* @param {
|
|
330
|
-
* @param {
|
|
331
|
-
* @param {
|
|
332
|
-
* @param {
|
|
333
|
-
* @param {
|
|
334
|
-
* @param {
|
|
335
|
-
* @param {
|
|
336
|
-
* @param {
|
|
337
|
-
* @param {
|
|
338
|
-
* @param {
|
|
339
|
-
* @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
|
|
340
425
|
* @memberof WebGL */
|
|
341
426
|
function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgba=-1, rgbaAdditive=0)
|
|
342
427
|
{
|
|
343
428
|
// flush if there is not enough room or if different blend mode
|
|
344
|
-
if (
|
|
429
|
+
if (glBatchCount >= gl_MAX_INSTANCES || glBatchAdditive !== glAdditive)
|
|
345
430
|
glFlush();
|
|
431
|
+
glSetInstancedMode();
|
|
346
432
|
|
|
347
|
-
|
|
433
|
+
glPolyMode = false;
|
|
434
|
+
let offset = glBatchCount++ * gl_INDICES_PER_INSTANCE;
|
|
348
435
|
glPositionData[offset++] = x;
|
|
349
436
|
glPositionData[offset++] = y;
|
|
350
437
|
glPositionData[offset++] = sizeX;
|
|
@@ -356,4 +443,299 @@ function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgb
|
|
|
356
443
|
glColorData[offset++] = rgba;
|
|
357
444
|
glColorData[offset++] = rgbaAdditive;
|
|
358
445
|
glPositionData[offset++] = angle;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/** Transform and add a polygon to the gl draw list
|
|
449
|
+
* @param {Array} points - Array of Vector2 points
|
|
450
|
+
* @param {number} rgba - Color of the polygon as a 32-bit integer
|
|
451
|
+
* @param {number} x
|
|
452
|
+
* @param {number} y
|
|
453
|
+
* @param {number} sx
|
|
454
|
+
* @param {number} sy
|
|
455
|
+
* @param {number} angle
|
|
456
|
+
* @param {boolean} [tristrip] - should tristrip algorithm be used
|
|
457
|
+
* @memberof WebGL */
|
|
458
|
+
function glDrawPointsTransform(points, rgba, x, y, sx, sy, angle, tristrip=true)
|
|
459
|
+
{
|
|
460
|
+
const pointsOut = [];
|
|
461
|
+
for (const p of points)
|
|
462
|
+
{
|
|
463
|
+
// transform the point
|
|
464
|
+
const px = p.x*sx;
|
|
465
|
+
const py = p.y*sy;
|
|
466
|
+
const sa = Math.sin(-angle);
|
|
467
|
+
const ca = Math.cos(-angle);
|
|
468
|
+
pointsOut.push(vec2(x + ca*px - sa*py, y + sa*px + ca*py));
|
|
469
|
+
}
|
|
470
|
+
const drawPoints = tristrip ? glPolyStrip(pointsOut) : pointsOut;
|
|
471
|
+
glDrawPoints(drawPoints, rgba);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/** Transform and add a polygon to the gl draw list
|
|
475
|
+
* @param {Array} points - Array of Vector2 points
|
|
476
|
+
* @param {number} rgba - Color of the polygon as a 32-bit integer
|
|
477
|
+
* @param {number} lineWidth - Width of the outline
|
|
478
|
+
* @param {number} x
|
|
479
|
+
* @param {number} y
|
|
480
|
+
* @param {number} sx
|
|
481
|
+
* @param {number} sy
|
|
482
|
+
* @param {number} angle
|
|
483
|
+
* @memberof WebGL */
|
|
484
|
+
function glDrawOutlineTransform(points, rgba, lineWidth, x, y, sx, sy, angle)
|
|
485
|
+
{
|
|
486
|
+
const outlinePoints = glMakeOutline(points, lineWidth);
|
|
487
|
+
glDrawPointsTransform(outlinePoints, rgba, x, y, sx, sy, angle, false);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/** Add a polygon to the gl draw list
|
|
491
|
+
* @param {Array} points - Array of Vector2 points in triangle strip order
|
|
492
|
+
* @param {number} rgba - Color of the polygon as a 32-bit integer
|
|
493
|
+
* @memberof WebGL */
|
|
494
|
+
function glDrawPoints(points, rgba)
|
|
495
|
+
{
|
|
496
|
+
if (!glEnable || points.length < 3)
|
|
497
|
+
return; // needs at least 3 points to have area
|
|
498
|
+
|
|
499
|
+
// add 2 degenerate verts if batching with existing polys to separate them
|
|
500
|
+
const needsBridge = glPolyMode && glBatchCount > 0;
|
|
501
|
+
const bridgeVerts = needsBridge ? 2 : 0;
|
|
502
|
+
const vertCount = points.length + bridgeVerts;
|
|
503
|
+
|
|
504
|
+
// flush if there is not enough room or if different blend mode
|
|
505
|
+
if (!glPolyMode || glBatchCount+vertCount >= gl_MAX_POLY_VERTEXES || glBatchAdditive !== glAdditive)
|
|
506
|
+
glFlush();
|
|
507
|
+
glSetPolyMode();
|
|
508
|
+
|
|
509
|
+
let offset = glBatchCount * gl_INDICES_PER_POLY_VERTEX;
|
|
510
|
+
|
|
511
|
+
// add degenerate bridge if needed (repeat last vertex of previous poly, then first of new poly)
|
|
512
|
+
if (needsBridge)
|
|
513
|
+
{
|
|
514
|
+
// repeat last vertex from previous batch (it's at offset - 3)
|
|
515
|
+
const prevOffset = offset - 3;
|
|
516
|
+
glPositionData[offset++] = glPositionData[prevOffset];
|
|
517
|
+
glPositionData[offset++] = glPositionData[prevOffset + 1];
|
|
518
|
+
glColorData[offset++] = glColorData[prevOffset + 2];
|
|
519
|
+
|
|
520
|
+
// repeat first vertex of new poly
|
|
521
|
+
glPositionData[offset++] = points[0].x;
|
|
522
|
+
glPositionData[offset++] = points[0].y;
|
|
523
|
+
glColorData[offset++] = rgba;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// write vertices - they're already in triangle strip order
|
|
527
|
+
for (const point of points)
|
|
528
|
+
{
|
|
529
|
+
glPositionData[offset++] = point.x;
|
|
530
|
+
glPositionData[offset++] = point.y;
|
|
531
|
+
glColorData[offset++] = rgba;
|
|
532
|
+
}
|
|
533
|
+
glBatchCount += vertCount;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// WebGL internal function to convert polygon to outline triangle strip
|
|
537
|
+
function glMakeOutline(points, width)
|
|
538
|
+
{
|
|
539
|
+
if (points.length < 2)
|
|
540
|
+
return [];
|
|
541
|
+
|
|
542
|
+
const halfWidth = width / 2;
|
|
543
|
+
const strip = [];
|
|
544
|
+
const n = points.length;
|
|
545
|
+
const e = 1e-6;
|
|
546
|
+
for (let i = 0; i < n; i++)
|
|
547
|
+
{
|
|
548
|
+
// for each vertex, calculate normal based on adjacent edges
|
|
549
|
+
const prev = points[(i - 1 + n) % n];
|
|
550
|
+
const curr = points[i];
|
|
551
|
+
const next = points[(i + 1) % n];
|
|
552
|
+
|
|
553
|
+
// direction from previous to current
|
|
554
|
+
const dx1 = curr.x - prev.x;
|
|
555
|
+
const dy1 = curr.y - prev.y;
|
|
556
|
+
const len1 = (dx1*dx1 + dy1*dy1)**.5;
|
|
557
|
+
|
|
558
|
+
// direction from current to next
|
|
559
|
+
const dx2 = next.x - curr.x;
|
|
560
|
+
const dy2 = next.y - curr.y;
|
|
561
|
+
const len2 = (dx2*dx2 + dy2*dy2)**.5;
|
|
562
|
+
|
|
563
|
+
if (len1 < e && len2 < e)
|
|
564
|
+
continue; // skip degenerate point
|
|
565
|
+
|
|
566
|
+
// calculate perpendicular normals for each edge
|
|
567
|
+
const nx1 = len1 > e ? -dy1 / len1 : 0;
|
|
568
|
+
const ny1 = len1 > e ? dx1 / len1 : 0;
|
|
569
|
+
const nx2 = len2 > e ? -dy2 / len2 : 0;
|
|
570
|
+
const ny2 = len2 > e ? dx2 / len2 : 0;
|
|
571
|
+
|
|
572
|
+
// average the normals for miter
|
|
573
|
+
let nx = nx1 + nx2;
|
|
574
|
+
let ny = ny1 + ny2;
|
|
575
|
+
const nlen = (nx*nx + ny*ny)**.5;
|
|
576
|
+
if (nlen < e)
|
|
577
|
+
{
|
|
578
|
+
// 180 degree turn - use perpendicular
|
|
579
|
+
nx = nx1;
|
|
580
|
+
ny = ny1;
|
|
581
|
+
}
|
|
582
|
+
else
|
|
583
|
+
{
|
|
584
|
+
// calculate miter length
|
|
585
|
+
nx /= nlen;
|
|
586
|
+
ny /= nlen;
|
|
587
|
+
const dot = nx1 * nx + ny1 * ny;
|
|
588
|
+
if (dot > e)
|
|
589
|
+
{
|
|
590
|
+
// scale normal by miter length
|
|
591
|
+
const miterLength = 1 / dot;
|
|
592
|
+
nx *= miterLength;
|
|
593
|
+
ny *= miterLength;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// create inner and outer points along the normal
|
|
598
|
+
const inner = vec2(curr.x - nx * halfWidth, curr.y - ny * halfWidth);
|
|
599
|
+
const outer = vec2(curr.x + nx * halfWidth, curr.y + ny * halfWidth);
|
|
600
|
+
strip.push(inner);
|
|
601
|
+
strip.push(outer);
|
|
602
|
+
}
|
|
603
|
+
if (strip.length > 1)
|
|
604
|
+
{
|
|
605
|
+
// close the loop
|
|
606
|
+
strip.push(strip[0]);
|
|
607
|
+
strip.push(strip[1]);
|
|
608
|
+
}
|
|
609
|
+
return strip;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// WebGL internal function to convert polys to tri strips
|
|
613
|
+
function glPolyStrip(points)
|
|
614
|
+
{
|
|
615
|
+
// validate input
|
|
616
|
+
if (points.length < 3)
|
|
617
|
+
return [];
|
|
618
|
+
|
|
619
|
+
// cross product helper: (b-a) x (c-a)
|
|
620
|
+
const cross = (a,b,c)=> (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
|
|
621
|
+
|
|
622
|
+
// calculate signed area of polygon
|
|
623
|
+
const signedArea = (poly)=>
|
|
624
|
+
{
|
|
625
|
+
let area = 0;
|
|
626
|
+
for (let i = poly.length; i--;)
|
|
627
|
+
{
|
|
628
|
+
const j = (i+1) % poly.length;
|
|
629
|
+
area += poly[i].cross(poly[j]);
|
|
630
|
+
}
|
|
631
|
+
return area;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// ensure counter-clockwise winding
|
|
635
|
+
if (signedArea(points) < 0)
|
|
636
|
+
points = points.reverse();
|
|
637
|
+
|
|
638
|
+
// tolerance constants
|
|
639
|
+
const e = 1e-10;
|
|
640
|
+
|
|
641
|
+
// check if point is inside triangle
|
|
642
|
+
const pointInTriangle = (p, a, b, c)=>
|
|
643
|
+
{
|
|
644
|
+
const c1 = cross(a, b, p);
|
|
645
|
+
const c2 = cross(b, c, p);
|
|
646
|
+
const c3 = cross(c, a, p);
|
|
647
|
+
const negative = (c1<-e?1:0) + (c2<-e?1:0) + (c3<-e?1:0);
|
|
648
|
+
const positive = (c1> e?1:0) + (c2> e?1:0) + (c3> e?1:0);
|
|
649
|
+
return !(negative && positive);
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
// ear clipping triangulation
|
|
653
|
+
const indices = [];
|
|
654
|
+
for (let i = 0; i < points.length; ++i)
|
|
655
|
+
indices[i] = i;
|
|
656
|
+
const triangles = [];
|
|
657
|
+
let attempts = 0;
|
|
658
|
+
const maxAttempts = points.length ** 2 + 100;
|
|
659
|
+
while (indices.length > 3 && attempts++ < maxAttempts)
|
|
660
|
+
{
|
|
661
|
+
let foundEar = false;
|
|
662
|
+
for (let i = indices.length; --i;)
|
|
663
|
+
{
|
|
664
|
+
const i0 = indices[(i + indices.length - 1) % indices.length];
|
|
665
|
+
const i1 = indices[i];
|
|
666
|
+
const i2 = indices[(i + 1) % indices.length];
|
|
667
|
+
const a = points[i0], b = points[i1], c = points[i2];
|
|
668
|
+
|
|
669
|
+
// check if convex
|
|
670
|
+
if (cross(a, b, c) < e)
|
|
671
|
+
continue;
|
|
672
|
+
|
|
673
|
+
// check if any other point is inside
|
|
674
|
+
let hasInside = false;
|
|
675
|
+
for (let j = 0; j < indices.length; j++)
|
|
676
|
+
{
|
|
677
|
+
const k = indices[j];
|
|
678
|
+
if (k === i0 || k === i1 || k === i2)
|
|
679
|
+
continue;
|
|
680
|
+
const p = points[k];
|
|
681
|
+
hasInside = pointInTriangle(p, a, b, c);
|
|
682
|
+
if (hasInside)
|
|
683
|
+
break;
|
|
684
|
+
}
|
|
685
|
+
if (hasInside)
|
|
686
|
+
continue;
|
|
687
|
+
|
|
688
|
+
// found valid ear
|
|
689
|
+
triangles.push([i0, i1, i2]);
|
|
690
|
+
indices.splice(i, 1);
|
|
691
|
+
foundEar = true;
|
|
692
|
+
break;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// fallback for degenerate cases
|
|
696
|
+
if (!foundEar)
|
|
697
|
+
{
|
|
698
|
+
let worstIndex = -1, worstValue = Infinity;
|
|
699
|
+
for (let i = indices.length; --i;)
|
|
700
|
+
{
|
|
701
|
+
const i0 = indices[(i + indices.length - 1) % indices.length];
|
|
702
|
+
const i1 = indices[i];
|
|
703
|
+
const i2 = indices[(i + 1) % indices.length];
|
|
704
|
+
const value = abs(cross(points[i0], points[i1], points[i2]));
|
|
705
|
+
if (value < worstValue)
|
|
706
|
+
{
|
|
707
|
+
worstValue = value;
|
|
708
|
+
worstIndex = i;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (worstIndex < 0)
|
|
712
|
+
break;
|
|
713
|
+
|
|
714
|
+
const i0 = indices[(worstIndex + indices.length - 1) % indices.length];
|
|
715
|
+
const i1 = indices[worstIndex];
|
|
716
|
+
const i2 = indices[(worstIndex + 1) % indices.length];
|
|
717
|
+
triangles.push([i0, i1, i2]);
|
|
718
|
+
indices.splice(worstIndex, 1);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// add final triangle
|
|
723
|
+
if (indices.length === 3)
|
|
724
|
+
triangles.push([indices[0], indices[1], indices[2]]);
|
|
725
|
+
if (!triangles.length)
|
|
726
|
+
return [];
|
|
727
|
+
|
|
728
|
+
// convert triangles to triangle strip with degenerate connectors
|
|
729
|
+
const strip = [];
|
|
730
|
+
let [a0, b0, c0] = triangles[0];
|
|
731
|
+
strip.push(points[a0], points[b0], points[c0]);
|
|
732
|
+
for (let i = 1; i < triangles.length; i++)
|
|
733
|
+
{
|
|
734
|
+
// add degenerate bridge from last vertex to first of new triangle
|
|
735
|
+
const [a, b, c] = triangles[i];
|
|
736
|
+
strip.push(points[c0], points[a]);
|
|
737
|
+
strip.push(points[a], points[b], points[c]);
|
|
738
|
+
c0 = c;
|
|
739
|
+
}
|
|
740
|
+
return strip;
|
|
359
741
|
}
|
|
File without changes
|