littlejsengine 1.8.9 → 1.9.0
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 +17 -17
- package/build/littlejs.d.ts +312 -250
- package/build/littlejs.esm.js +633 -599
- package/build/littlejs.esm.min.js +1 -1
- package/build/littlejs.js +632 -599
- package/build/littlejs.min.js +1 -1
- package/build/littlejs.release.js +567 -535
- package/examples/breakout/game.js +4 -4
- package/examples/breakout/index.html +3 -3
- package/examples/breakoutTutorial/index.html +2 -2
- package/examples/electron/game.js +1 -1
- package/examples/electron/index.html +2 -2
- package/examples/favicon.png +0 -0
- package/examples/js13k/index.html +13 -13
- package/examples/module/game.js +1 -1
- package/examples/module/index.html +1 -1
- package/examples/particles/index.html +1 -1
- package/examples/platformer/game.js +6 -6
- package/examples/platformer/gameCharacter.js +293 -0
- package/examples/platformer/gameEffects.js +8 -5
- package/examples/platformer/gameObjects.js +13 -13
- package/examples/platformer/gamePlayer.js +9 -293
- package/examples/platformer/index.html +7 -6
- package/examples/puzzle/game.js +3 -2
- package/examples/puzzle/index.html +2 -2
- package/examples/starter/game.js +2 -2
- package/examples/starter/index.html +13 -13
- package/examples/stress/index.html +34 -26
- package/examples/typescript/index.html +1 -1
- package/package.json +1 -1
- package/src/engine.js +28 -28
- package/src/engineAudio.js +57 -57
- package/src/engineDebug.js +66 -65
- package/src/engineDraw.js +47 -56
- package/src/engineExport.js +1 -0
- package/src/engineInput.js +57 -40
- package/src/engineMedals.js +32 -29
- package/src/engineObject.js +41 -26
- package/src/engineParticles.js +98 -72
- package/src/engineRelease.js +1 -1
- package/src/engineSettings.js +22 -22
- package/src/engineTileLayer.js +34 -33
- package/src/engineUtilities.js +44 -44
- package/src/engineWebGL.js +105 -126
- package/src/jsconfig.json +10 -0
package/src/engineUtilities.js
CHANGED
|
@@ -44,15 +44,15 @@ function sign(value) { return Math.sign(value); }
|
|
|
44
44
|
|
|
45
45
|
/** Returns first parm modulo the second param, but adjusted so negative numbers work as expected
|
|
46
46
|
* @param {Number} dividend
|
|
47
|
-
* @param {Number} [divisor
|
|
47
|
+
* @param {Number} [divisor]
|
|
48
48
|
* @return {Number}
|
|
49
49
|
* @memberof Utilities */
|
|
50
50
|
function mod(dividend, divisor=1) { return ((dividend % divisor) + divisor) % divisor; }
|
|
51
51
|
|
|
52
52
|
/** Clamps the value beween max and min
|
|
53
53
|
* @param {Number} value
|
|
54
|
-
* @param {Number} [min
|
|
55
|
-
* @param {Number} [max
|
|
54
|
+
* @param {Number} [min]
|
|
55
|
+
* @param {Number} [max]
|
|
56
56
|
* @return {Number}
|
|
57
57
|
* @memberof Utilities */
|
|
58
58
|
function clamp(value, min=0, max=1) { return value < min ? min : value > max ? max : value; }
|
|
@@ -77,7 +77,7 @@ function lerp(percent, valueA, valueB) { return valueA + clamp(percent) * (value
|
|
|
77
77
|
/** Returns signed wrapped distance between the two values passed in
|
|
78
78
|
* @param {Number} valueA
|
|
79
79
|
* @param {Number} valueB
|
|
80
|
-
* @param {Number} [wrapSize
|
|
80
|
+
* @param {Number} [wrapSize]
|
|
81
81
|
* @returns {Number}
|
|
82
82
|
* @memberof Utilities */
|
|
83
83
|
function distanceWrap(valueA, valueB, wrapSize=1)
|
|
@@ -87,7 +87,7 @@ function distanceWrap(valueA, valueB, wrapSize=1)
|
|
|
87
87
|
* @param {Number} percent
|
|
88
88
|
* @param {Number} valueA
|
|
89
89
|
* @param {Number} valueB
|
|
90
|
-
* @param {Number} [wrapSize
|
|
90
|
+
* @param {Number} [wrapSize]
|
|
91
91
|
* @returns {Number}
|
|
92
92
|
* @memberof Utilities */
|
|
93
93
|
function lerpWrap(percent, valueA, valueB, wrapSize=1)
|
|
@@ -98,7 +98,7 @@ function lerpWrap(percent, valueA, valueB, wrapSize=1)
|
|
|
98
98
|
* @param {Number} angleB
|
|
99
99
|
* @returns {Number}
|
|
100
100
|
* @memberof Utilities */
|
|
101
|
-
function distanceAngle(angleA, angleB) { distanceWrap(angleA, angleB, 2*PI); }
|
|
101
|
+
function distanceAngle(angleA, angleB) { return distanceWrap(angleA, angleB, 2*PI); }
|
|
102
102
|
|
|
103
103
|
/** Linearly interpolates between the angles passed in with wrappping
|
|
104
104
|
* @param {Number} percent
|
|
@@ -134,10 +134,10 @@ function isOverlapping(pointA, sizeA, pointB, sizeB)
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
/** Returns an oscillating wave between 0 and amplitude with frequency of 1 Hz by default
|
|
137
|
-
* @param {Number} [frequency
|
|
138
|
-
* @param {Number} [amplitude
|
|
139
|
-
* @param {Number} [t=time]
|
|
140
|
-
* @return {Number}
|
|
137
|
+
* @param {Number} [frequency] - Frequency of the wave in Hz
|
|
138
|
+
* @param {Number} [amplitude] - Amplitude (max height) of the wave
|
|
139
|
+
* @param {Number} [t=time] - Value to use for time of the wave
|
|
140
|
+
* @return {Number} - Value waving between 0 and amplitude
|
|
141
141
|
* @memberof Utilities */
|
|
142
142
|
function wave(frequency=1, amplitude=1, t=time)
|
|
143
143
|
{ return amplitude/2 * (1 - Math.cos(t*frequency*2*PI)); }
|
|
@@ -154,15 +154,15 @@ function formatTime(t) { return (t/60|0) + ':' + (t%60<10?'0':'') + (t%60|0); }
|
|
|
154
154
|
* @namespace Random */
|
|
155
155
|
|
|
156
156
|
/** Returns a random value between the two values passed in
|
|
157
|
-
* @param {Number} [valueA
|
|
158
|
-
* @param {Number} [valueB
|
|
157
|
+
* @param {Number} [valueA]
|
|
158
|
+
* @param {Number} [valueB]
|
|
159
159
|
* @return {Number}
|
|
160
160
|
* @memberof Random */
|
|
161
161
|
function rand(valueA=1, valueB=0) { return valueB + Math.random() * (valueA-valueB); }
|
|
162
162
|
|
|
163
163
|
/** Returns a floored random value the two values passed in
|
|
164
164
|
* @param {Number} valueA
|
|
165
|
-
* @param {Number} [valueB
|
|
165
|
+
* @param {Number} [valueB]
|
|
166
166
|
* @return {Number}
|
|
167
167
|
* @memberof Random */
|
|
168
168
|
function randInt(valueA, valueB=0) { return Math.floor(rand(valueA,valueB)); }
|
|
@@ -173,14 +173,14 @@ function randInt(valueA, valueB=0) { return Math.floor(rand(valueA,valueB)); }
|
|
|
173
173
|
function randSign() { return randInt(2) * 2 - 1; }
|
|
174
174
|
|
|
175
175
|
/** Returns a random Vector2 with the passed in length
|
|
176
|
-
* @param {Number} [length
|
|
176
|
+
* @param {Number} [length]
|
|
177
177
|
* @return {Vector2}
|
|
178
178
|
* @memberof Random */
|
|
179
179
|
function randVector(length=1) { return new Vector2().setAngle(rand(2*PI), length); }
|
|
180
180
|
|
|
181
181
|
/** Returns a random Vector2 within a circular shape
|
|
182
|
-
* @param {Number} [radius
|
|
183
|
-
* @param {Number} [minRadius
|
|
182
|
+
* @param {Number} [radius]
|
|
183
|
+
* @param {Number} [minRadius]
|
|
184
184
|
* @return {Vector2}
|
|
185
185
|
* @memberof Random */
|
|
186
186
|
function randInCircle(radius=1, minRadius=0)
|
|
@@ -192,7 +192,7 @@ function randInCircle(radius=1, minRadius=0)
|
|
|
192
192
|
* @param {Boolean} [linear]
|
|
193
193
|
* @return {Color}
|
|
194
194
|
* @memberof Random */
|
|
195
|
-
function randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear)
|
|
195
|
+
function randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear=false)
|
|
196
196
|
{
|
|
197
197
|
return linear ? colorA.lerp(colorB, rand()) :
|
|
198
198
|
new Color(rand(colorA.r,colorB.r), rand(colorA.g,colorB.g), rand(colorA.b,colorB.b), rand(colorA.a,colorB.a));
|
|
@@ -221,8 +221,8 @@ class RandomGenerator
|
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
/** Returns a seeded random value between the two values passed in
|
|
224
|
-
* @param {Number} [valueA
|
|
225
|
-
* @param {Number} [valueB
|
|
224
|
+
* @param {Number} [valueA]
|
|
225
|
+
* @param {Number} [valueB]
|
|
226
226
|
* @return {Number} */
|
|
227
227
|
float(valueA=1, valueB=0)
|
|
228
228
|
{
|
|
@@ -235,7 +235,7 @@ class RandomGenerator
|
|
|
235
235
|
|
|
236
236
|
/** Returns a floored seeded random value the two values passed in
|
|
237
237
|
* @param {Number} valueA
|
|
238
|
-
* @param {Number} [valueB
|
|
238
|
+
* @param {Number} [valueB]
|
|
239
239
|
* @return {Number} */
|
|
240
240
|
int(valueA, valueB=0) { return Math.floor(this.float(valueA, valueB)); }
|
|
241
241
|
|
|
@@ -248,8 +248,8 @@ class RandomGenerator
|
|
|
248
248
|
|
|
249
249
|
/**
|
|
250
250
|
* Create a 2d vector, can take another Vector2 to copy, 2 scalars, or 1 scalar
|
|
251
|
-
* @param {(Number|Vector2)} [x
|
|
252
|
-
* @param {Number} [y
|
|
251
|
+
* @param {(Number|Vector2)} [x]
|
|
252
|
+
* @param {Number} [y]
|
|
253
253
|
* @return {Vector2}
|
|
254
254
|
* @example
|
|
255
255
|
* let a = vec2(0, 1); // vector with coordinates (0, 1)
|
|
@@ -259,15 +259,15 @@ class RandomGenerator
|
|
|
259
259
|
* @memberof Utilities
|
|
260
260
|
*/
|
|
261
261
|
function vec2(x=0, y)
|
|
262
|
-
{ return x
|
|
262
|
+
{ return typeof x === 'number'? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y); }
|
|
263
263
|
|
|
264
264
|
/**
|
|
265
265
|
* Check if object is a valid Vector2
|
|
266
|
-
* @param {
|
|
266
|
+
* @param {any} v
|
|
267
267
|
* @return {Boolean}
|
|
268
268
|
* @memberof Utilities
|
|
269
269
|
*/
|
|
270
|
-
function isVector2(v) { return
|
|
270
|
+
function isVector2(v) { return typeof v === 'object' && typeof v.x === 'number' && typeof v.y === 'number'; }
|
|
271
271
|
|
|
272
272
|
/**
|
|
273
273
|
* 2D Vector object with vector math library
|
|
@@ -281,8 +281,8 @@ function isVector2(v) { return !isNaN(v.x) && !isNaN(v.y); }
|
|
|
281
281
|
class Vector2
|
|
282
282
|
{
|
|
283
283
|
/** Create a 2D vector with the x and y passed in, can also be created with vec2()
|
|
284
|
-
* @param {Number} [x
|
|
285
|
-
* @param {Number} [y
|
|
284
|
+
* @param {Number} [x] - X axis location
|
|
285
|
+
* @param {Number} [y] - Y axis location */
|
|
286
286
|
constructor(x=0, y=0)
|
|
287
287
|
{
|
|
288
288
|
/** @property {Number} - X axis location */
|
|
@@ -339,12 +339,12 @@ class Vector2
|
|
|
339
339
|
distanceSquared(v) { return (this.x - v.x)**2 + (this.y - v.y)**2; }
|
|
340
340
|
|
|
341
341
|
/** Returns a new vector in same direction as this one with the length passed in
|
|
342
|
-
* @param {Number} [length
|
|
342
|
+
* @param {Number} [length]
|
|
343
343
|
* @return {Vector2} */
|
|
344
344
|
normalize(length=1) { const l = this.length(); return l ? this.scale(length/l) : new Vector2(0, length); }
|
|
345
345
|
|
|
346
346
|
/** Returns a new vector clamped to length passed in
|
|
347
|
-
* @param {Number} [length
|
|
347
|
+
* @param {Number} [length]
|
|
348
348
|
* @return {Vector2} */
|
|
349
349
|
clampLength(length=1) { const l = this.length(); return l > length ? this.scale(length/l) : this; }
|
|
350
350
|
|
|
@@ -363,8 +363,8 @@ class Vector2
|
|
|
363
363
|
angle() { return Math.atan2(this.x, this.y); }
|
|
364
364
|
|
|
365
365
|
/** Sets this vector with angle and length passed in
|
|
366
|
-
* @param {Number} [angle
|
|
367
|
-
* @param {Number} [length
|
|
366
|
+
* @param {Number} [angle]
|
|
367
|
+
* @param {Number} [length]
|
|
368
368
|
* @return {Vector2} */
|
|
369
369
|
setAngle(angle=0, length=1)
|
|
370
370
|
{ this.x = length*Math.sin(angle); this.y = length*Math.cos(angle); return this; }
|
|
@@ -449,10 +449,10 @@ function hsl(h, s, l, a) { return new Color().setHSLA(h, s, l, a); }
|
|
|
449
449
|
class Color
|
|
450
450
|
{
|
|
451
451
|
/** Create a color with the rgba components passed in, white by default
|
|
452
|
-
* @param {Number} [r
|
|
453
|
-
* @param {Number} [g
|
|
454
|
-
* @param {Number} [b
|
|
455
|
-
* @param {Number} [a
|
|
452
|
+
* @param {Number} [r] - red
|
|
453
|
+
* @param {Number} [g] - green
|
|
454
|
+
* @param {Number} [b] - blue
|
|
455
|
+
* @param {Number} [a] - alpha*/
|
|
456
456
|
constructor(r=1, g=1, b=1, a=1)
|
|
457
457
|
{
|
|
458
458
|
/** @property {Number} - Red */
|
|
@@ -507,10 +507,10 @@ class Color
|
|
|
507
507
|
lerp(c, percent) { return this.add(c.subtract(this).scale(clamp(percent))); }
|
|
508
508
|
|
|
509
509
|
/** Sets this color given a hue, saturation, lightness, and alpha
|
|
510
|
-
* @param {Number} [h
|
|
511
|
-
* @param {Number} [s
|
|
512
|
-
* @param {Number} [l
|
|
513
|
-
* @param {Number} [a
|
|
510
|
+
* @param {Number} [h] - hue
|
|
511
|
+
* @param {Number} [s] - saturation
|
|
512
|
+
* @param {Number} [l] - lightness
|
|
513
|
+
* @param {Number} [a] - alpha
|
|
514
514
|
* @return {Color} */
|
|
515
515
|
setHSLA(h=0, s=0, l=1, a=1)
|
|
516
516
|
{
|
|
@@ -556,8 +556,8 @@ class Color
|
|
|
556
556
|
}
|
|
557
557
|
|
|
558
558
|
/** Returns a new color that has each component randomly adjusted
|
|
559
|
-
* @param {Number} [amount
|
|
560
|
-
* @param {Number} [alphaAmount
|
|
559
|
+
* @param {Number} [amount]
|
|
560
|
+
* @param {Number} [alphaAmount]
|
|
561
561
|
* @return {Color} */
|
|
562
562
|
mutate(amount=.05, alphaAmount=0)
|
|
563
563
|
{
|
|
@@ -571,9 +571,9 @@ class Color
|
|
|
571
571
|
}
|
|
572
572
|
|
|
573
573
|
/** Returns this color expressed as a hex color code
|
|
574
|
-
* @param {Boolean} [useAlpha
|
|
574
|
+
* @param {Boolean} [useAlpha] - if alpha should be included in result
|
|
575
575
|
* @return {String} */
|
|
576
|
-
toString(useAlpha =
|
|
576
|
+
toString(useAlpha = true)
|
|
577
577
|
{
|
|
578
578
|
const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
|
|
579
579
|
return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
|
|
@@ -622,7 +622,7 @@ class Timer
|
|
|
622
622
|
constructor(timeLeft) { this.time = timeLeft == undefined ? undefined : time + timeLeft; this.setTime = timeLeft; }
|
|
623
623
|
|
|
624
624
|
/** Set the timer with seconds passed in
|
|
625
|
-
* @param {Number} [timeLeft
|
|
625
|
+
* @param {Number} [timeLeft] - How much time left before the timer is elapsed in seconds */
|
|
626
626
|
set(timeLeft=0) { this.time = time + timeLeft; this.setTime = timeLeft; }
|
|
627
627
|
|
|
628
628
|
/** Unset the timer */
|
package/src/engineWebGL.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
2
|
* LittleJS WebGL Interface
|
|
3
3
|
* - All webgl used by the engine is wrapped up here
|
|
4
4
|
* - For normal stuff you won't need to see or call anything in this file
|
|
@@ -17,13 +17,13 @@
|
|
|
17
17
|
* @memberof WebGL */
|
|
18
18
|
let glCanvas;
|
|
19
19
|
|
|
20
|
-
/** 2d context for glCanvas
|
|
21
|
-
* @type {
|
|
20
|
+
/** 2d context for glCanvas
|
|
21
|
+
* @type {WebGL2RenderingContext}
|
|
22
22
|
* @memberof WebGL */
|
|
23
23
|
let glContext;
|
|
24
24
|
|
|
25
25
|
// WebGL internal variables not exposed to documentation
|
|
26
|
-
let
|
|
26
|
+
let glShader, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glInstanceCount, glAdditive, glBatchAdditive;
|
|
27
27
|
|
|
28
28
|
///////////////////////////////////////////////////////////////////////////////
|
|
29
29
|
|
|
@@ -39,73 +39,89 @@ function glInit()
|
|
|
39
39
|
|
|
40
40
|
// setup vertex and fragment shaders
|
|
41
41
|
glShader = glCreateProgram(
|
|
42
|
-
'#version 300 es\n' +
|
|
43
|
-
'precision highp float;'+
|
|
44
|
-
'uniform mat4 m;'+
|
|
45
|
-
'in
|
|
46
|
-
'
|
|
47
|
-
'
|
|
48
|
-
'
|
|
49
|
-
'
|
|
50
|
-
'
|
|
42
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
43
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
44
|
+
'uniform mat4 m;'+ // transform matrix
|
|
45
|
+
'in vec2 g;'+ // geometry
|
|
46
|
+
'in vec4 p,u,c,a;'+ // position/size, uvs, color, additiveColor
|
|
47
|
+
'in float r;'+ // rotation
|
|
48
|
+
'out vec2 v;'+ // return uv, color, additiveColor
|
|
49
|
+
'out vec4 d,e;'+ // return uv, color, additiveColor
|
|
50
|
+
'void main(){'+ // shader entry point
|
|
51
|
+
'vec2 s=(g-.5)*p.zw;'+ // get size offset
|
|
52
|
+
'gl_Position=m*vec4(p.xy+s*cos(r)-vec2(-s.y,s)*sin(r),1,1);'+ // transform position
|
|
53
|
+
'v=mix(u.xw,u.zy,g);'+ // pass uv to fragment shader
|
|
54
|
+
'd=c;e=a;'+ // pass colors to fragment shader
|
|
55
|
+
'}' // end of shader
|
|
51
56
|
,
|
|
52
|
-
'#version 300 es\n' +
|
|
53
|
-
'precision highp float;'+
|
|
54
|
-
'in
|
|
55
|
-
'
|
|
56
|
-
'
|
|
57
|
-
'
|
|
58
|
-
'
|
|
59
|
-
'
|
|
57
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
58
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
59
|
+
'in vec2 v;'+ // uv
|
|
60
|
+
'in vec4 d,e;'+ // color, additiveColor
|
|
61
|
+
'uniform sampler2D s;'+ // texture
|
|
62
|
+
'out vec4 c;'+ // out color
|
|
63
|
+
'void main(){'+ // shader entry point
|
|
64
|
+
'c=texture(s,v)*d+e;'+ // modulate texture by color plus additive
|
|
65
|
+
'}' // end of shader
|
|
60
66
|
);
|
|
61
67
|
|
|
62
68
|
// init buffers
|
|
63
|
-
|
|
64
|
-
glPositionData = new Float32Array(
|
|
65
|
-
glColorData = new Uint32Array(
|
|
69
|
+
const glInstanceData = new ArrayBuffer(gl_INSTANCE_BUFFER_SIZE);
|
|
70
|
+
glPositionData = new Float32Array(glInstanceData);
|
|
71
|
+
glColorData = new Uint32Array(glInstanceData);
|
|
66
72
|
glArrayBuffer = glContext.createBuffer();
|
|
67
|
-
|
|
73
|
+
glGeometryBuffer = glContext.createBuffer();
|
|
74
|
+
|
|
75
|
+
// create the geometry buffer, triangle strip square
|
|
76
|
+
const geometry = new Float32Array([glInstanceCount=0,0,1,0,0,1,1,1]);
|
|
77
|
+
glContext.bindBuffer(gl_ARRAY_BUFFER, glGeometryBuffer);
|
|
78
|
+
glContext.bufferData(gl_ARRAY_BUFFER, geometry, gl_STATIC_DRAW);
|
|
68
79
|
}
|
|
69
80
|
|
|
70
81
|
// Setup render each frame, called automatically by engine
|
|
71
82
|
function glPreRender()
|
|
72
83
|
{
|
|
73
84
|
// clear and set to same size as main canvas
|
|
74
|
-
glContext.viewport(0, 0, glCanvas.width
|
|
85
|
+
glContext.viewport(0, 0, glCanvas.width=mainCanvas.width, glCanvas.height=mainCanvas.height);
|
|
75
86
|
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
76
87
|
|
|
77
88
|
// set up the shader
|
|
78
89
|
glContext.useProgram(glShader);
|
|
79
90
|
glContext.activeTexture(gl_TEXTURE0);
|
|
80
91
|
glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
|
|
81
|
-
|
|
82
|
-
glContext.bufferData(gl_ARRAY_BUFFER, gl_VERTEX_BUFFER_SIZE, gl_DYNAMIC_DRAW);
|
|
83
|
-
glAdditive = 0;
|
|
84
|
-
|
|
92
|
+
|
|
85
93
|
// set vertex attributes
|
|
86
|
-
let offset = 0;
|
|
87
|
-
|
|
94
|
+
let offset = glAdditive = glBatchAdditive = 0;
|
|
95
|
+
let initVertexAttribArray = (name, type, typeSize, size)=>
|
|
88
96
|
{
|
|
89
97
|
const location = glContext.getAttribLocation(glShader, name);
|
|
98
|
+
const stride = typeSize && gl_INSTANCE_BYTE_STRIDE; // only if not geometry
|
|
99
|
+
const divisor = typeSize && 1; // only if not geometry
|
|
100
|
+
const normalize = typeSize==1; // only if color
|
|
90
101
|
glContext.enableVertexAttribArray(location);
|
|
91
|
-
glContext.vertexAttribPointer(location, size, type, normalize,
|
|
102
|
+
glContext.vertexAttribPointer(location, size, type, normalize, stride, offset);
|
|
103
|
+
glContext.vertexAttribDivisor(location, divisor);
|
|
92
104
|
offset += size*typeSize;
|
|
93
105
|
}
|
|
94
|
-
|
|
95
|
-
initVertexAttribArray('
|
|
96
|
-
|
|
106
|
+
glContext.bindBuffer(gl_ARRAY_BUFFER, glGeometryBuffer);
|
|
107
|
+
initVertexAttribArray('g', gl_FLOAT, 0, 2); // geometry
|
|
108
|
+
glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
|
|
109
|
+
glContext.bufferData(gl_ARRAY_BUFFER, gl_INSTANCE_BUFFER_SIZE, gl_DYNAMIC_DRAW);
|
|
110
|
+
initVertexAttribArray('p', gl_FLOAT, 4, 4); // position & size
|
|
111
|
+
initVertexAttribArray('u', gl_FLOAT, 4, 4); // texture coords
|
|
112
|
+
initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4); // color
|
|
113
|
+
initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4); // additiveColor
|
|
114
|
+
initVertexAttribArray('r', gl_FLOAT, 4, 1); // rotation
|
|
97
115
|
|
|
98
116
|
// build the transform matrix
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
const cy = -1 - sy*cameraPos.y;
|
|
103
|
-
glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), 0,
|
|
117
|
+
const s = vec2(2*cameraScale).divide(mainCanvasSize);
|
|
118
|
+
const p = vec2(-1).subtract(cameraPos.multiply(s));
|
|
119
|
+
glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), false,
|
|
104
120
|
new Float32Array([
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
121
|
+
s.x, 0, 0, 0,
|
|
122
|
+
0, s.y, 0, 0,
|
|
123
|
+
1, 1, 1, 1,
|
|
124
|
+
p.x, p.y, 0, 0
|
|
109
125
|
])
|
|
110
126
|
);
|
|
111
127
|
}
|
|
@@ -126,7 +142,7 @@ function glSetTexture(texture)
|
|
|
126
142
|
|
|
127
143
|
/** Compile WebGL shader of the given type, will throw errors if in debug mode
|
|
128
144
|
* @param {String} source
|
|
129
|
-
* @param
|
|
145
|
+
* @param {Number} type
|
|
130
146
|
* @return {WebGLShader}
|
|
131
147
|
* @memberof WebGL */
|
|
132
148
|
function glCompileShader(source, type)
|
|
@@ -143,8 +159,8 @@ function glCompileShader(source, type)
|
|
|
143
159
|
}
|
|
144
160
|
|
|
145
161
|
/** Create WebGL program with given shaders
|
|
146
|
-
* @param {
|
|
147
|
-
* @param {
|
|
162
|
+
* @param {String} vsSource
|
|
163
|
+
* @param {String} fsSource
|
|
148
164
|
* @return {WebGLProgram}
|
|
149
165
|
* @memberof WebGL */
|
|
150
166
|
function glCreateProgram(vsSource, fsSource)
|
|
@@ -162,7 +178,7 @@ function glCreateProgram(vsSource, fsSource)
|
|
|
162
178
|
}
|
|
163
179
|
|
|
164
180
|
/** Create WebGL texture from an image and init the texture settings
|
|
165
|
-
* @param {
|
|
181
|
+
* @param {HTMLImageElement} image
|
|
166
182
|
* @return {WebGLTexture}
|
|
167
183
|
* @memberof WebGL */
|
|
168
184
|
function glCreateTexture(image)
|
|
@@ -172,7 +188,7 @@ function glCreateTexture(image)
|
|
|
172
188
|
glContext.bindTexture(gl_TEXTURE_2D, texture);
|
|
173
189
|
if (image)
|
|
174
190
|
glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
|
|
175
|
-
|
|
191
|
+
|
|
176
192
|
// use point filtering for pixelated rendering
|
|
177
193
|
const filter = canvasPixelated ? gl_NEAREST : gl_LINEAR;
|
|
178
194
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
|
|
@@ -187,29 +203,31 @@ function glCreateTexture(image)
|
|
|
187
203
|
* @memberof WebGL */
|
|
188
204
|
function glFlush()
|
|
189
205
|
{
|
|
190
|
-
if (!
|
|
206
|
+
if (!glInstanceCount) return;
|
|
191
207
|
|
|
192
208
|
const destBlend = glBatchAdditive ? gl_ONE : gl_ONE_MINUS_SRC_ALPHA;
|
|
193
209
|
glContext.blendFuncSeparate(gl_SRC_ALPHA, destBlend, gl_ONE, destBlend);
|
|
194
210
|
glContext.enable(gl_BLEND);
|
|
195
211
|
|
|
196
212
|
// draw all the sprites in the batch and reset the buffer
|
|
197
|
-
glContext.bufferSubData(gl_ARRAY_BUFFER, 0,
|
|
198
|
-
glContext.
|
|
199
|
-
|
|
213
|
+
glContext.bufferSubData(gl_ARRAY_BUFFER, 0, glPositionData);
|
|
214
|
+
glContext.drawArraysInstanced(gl_TRIANGLE_STRIP, 0, 4, glInstanceCount);
|
|
215
|
+
if (showWatermark)
|
|
216
|
+
drawCount += glInstanceCount;
|
|
217
|
+
glInstanceCount = 0;
|
|
200
218
|
glBatchAdditive = glAdditive;
|
|
201
219
|
}
|
|
202
220
|
|
|
203
221
|
/** Draw any sprites still in the buffer, copy to main canvas and clear
|
|
204
222
|
* @param {CanvasRenderingContext2D} context
|
|
205
|
-
* @param {Boolean} [forceDraw
|
|
223
|
+
* @param {Boolean} [forceDraw]
|
|
206
224
|
* @memberof WebGL */
|
|
207
|
-
function glCopyToContext(context, forceDraw)
|
|
225
|
+
function glCopyToContext(context, forceDraw=false)
|
|
208
226
|
{
|
|
209
|
-
if (!
|
|
210
|
-
|
|
227
|
+
if (!glInstanceCount && !forceDraw) return;
|
|
228
|
+
|
|
211
229
|
glFlush();
|
|
212
|
-
|
|
230
|
+
|
|
213
231
|
// do not draw in overlay mode because the canvas is visible
|
|
214
232
|
if (!glOverlay || forceDraw)
|
|
215
233
|
context.drawImage(glCanvas, 0, 0);
|
|
@@ -231,60 +249,22 @@ function glCopyToContext(context, forceDraw)
|
|
|
231
249
|
function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdditive=0)
|
|
232
250
|
{
|
|
233
251
|
// flush if there is not enough room or if different blend mode
|
|
234
|
-
|
|
235
|
-
if (glBatchCount >= gl_MAX_BATCH-vertCount || glBatchAdditive != glAdditive)
|
|
252
|
+
if (glInstanceCount >= gl_MAX_INSTANCES-1 || glBatchAdditive != glAdditive)
|
|
236
253
|
glFlush();
|
|
237
254
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
[
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
];
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
{
|
|
252
|
-
const j = clamp(i-1, 0, 3)*4; // degenerate tri at ends
|
|
253
|
-
glPositionData[offset++] = positionData[j+0];
|
|
254
|
-
glPositionData[offset++] = positionData[j+1];
|
|
255
|
-
glPositionData[offset++] = positionData[j+2];
|
|
256
|
-
glPositionData[offset++] = positionData[j+3];
|
|
257
|
-
glColorData[offset++] = rgba;
|
|
258
|
-
glColorData[offset++] = rgbaAdditive;
|
|
259
|
-
}
|
|
260
|
-
glBatchCount += vertCount;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/** Add a convex polygon to the gl draw list
|
|
264
|
-
* @param {Array} points - Array of Vector2 points
|
|
265
|
-
* @param {Number} rgba - Color of the polygon
|
|
266
|
-
* @memberof WebGL */
|
|
267
|
-
function glDrawPoints(points, rgba)
|
|
268
|
-
{
|
|
269
|
-
// flush if there is not enough room or if different blend mode
|
|
270
|
-
const vertCount = points.length + 2;
|
|
271
|
-
if (glBatchCount >= gl_MAX_BATCH-vertCount || glBatchAdditive != glAdditive)
|
|
272
|
-
glFlush();
|
|
273
|
-
|
|
274
|
-
// setup triangle strip from list of points
|
|
275
|
-
for(let i = vertCount, offset = glBatchCount * gl_INDICIES_PER_VERT; i--;)
|
|
276
|
-
{
|
|
277
|
-
const j = clamp(i-1, 0, vertCount-3); // degenerate tri at ends
|
|
278
|
-
const h = j>>1;
|
|
279
|
-
const point = points[j%2? h : vertCount-3-h];
|
|
280
|
-
glPositionData[offset++] = point.x;
|
|
281
|
-
glPositionData[offset++] = point.y;
|
|
282
|
-
glPositionData[offset++] = 0; // uvx
|
|
283
|
-
glPositionData[offset++] = 0; // uvy
|
|
284
|
-
glColorData[offset++] = 0; // nothing to tint
|
|
285
|
-
glColorData[offset++] = rgba; // apply rgba via additive
|
|
286
|
-
}
|
|
287
|
-
glBatchCount += vertCount;
|
|
255
|
+
let offset = glInstanceCount * gl_INDICIES_PER_INSTANCE;
|
|
256
|
+
glPositionData[offset++] = x;
|
|
257
|
+
glPositionData[offset++] = y;
|
|
258
|
+
glPositionData[offset++] = sizeX;
|
|
259
|
+
glPositionData[offset++] = sizeY;
|
|
260
|
+
glPositionData[offset++] = uv0X;
|
|
261
|
+
glPositionData[offset++] = uv0Y;
|
|
262
|
+
glPositionData[offset++] = uv1X;
|
|
263
|
+
glPositionData[offset++] = uv1Y;
|
|
264
|
+
glColorData[offset++] = rgba;
|
|
265
|
+
glColorData[offset++] = rgbaAdditive;
|
|
266
|
+
glPositionData[offset++] = angle;
|
|
267
|
+
glInstanceCount++;
|
|
288
268
|
}
|
|
289
269
|
|
|
290
270
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -298,7 +278,7 @@ let glPostShader, glPostArrayBuffer, glPostTexture, glPostIncludeOverlay;
|
|
|
298
278
|
* @memberof WebGL */
|
|
299
279
|
function glInitPostProcess(shaderCode, includeOverlay)
|
|
300
280
|
{
|
|
301
|
-
ASSERT(!glPostShader
|
|
281
|
+
ASSERT(!glPostShader, 'can only have 1 post effects shader');
|
|
302
282
|
|
|
303
283
|
if (!shaderCode) // default shader pass through
|
|
304
284
|
shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
|
|
@@ -309,14 +289,14 @@ function glInitPostProcess(shaderCode, includeOverlay)
|
|
|
309
289
|
'precision highp float;'+ // use highp for better accuracy
|
|
310
290
|
'in vec2 p;'+ // position
|
|
311
291
|
'void main(){'+ // shader entry point
|
|
312
|
-
'gl_Position=vec4(p
|
|
292
|
+
'gl_Position=vec4(p+p-1.,1,1);'+ // set position
|
|
313
293
|
'}' // end of shader
|
|
314
294
|
,
|
|
315
295
|
'#version 300 es\n' + // specify GLSL ES version
|
|
316
296
|
'precision highp float;'+ // use highp for better accuracy
|
|
317
297
|
'uniform sampler2D iChannel0;'+ // input texture
|
|
318
298
|
'uniform vec3 iResolution;'+ // size of output texture
|
|
319
|
-
'uniform float iTime;'+ // time
|
|
299
|
+
'uniform float iTime;'+ // time
|
|
320
300
|
'out vec4 c;'+ // out color
|
|
321
301
|
'\n' + shaderCode + '\n'+ // insert custom shader code
|
|
322
302
|
'void main(){'+ // shader entry point
|
|
@@ -327,7 +307,7 @@ function glInitPostProcess(shaderCode, includeOverlay)
|
|
|
327
307
|
|
|
328
308
|
// create buffer and texture
|
|
329
309
|
glPostArrayBuffer = glContext.createBuffer();
|
|
330
|
-
glPostTexture = glCreateTexture();
|
|
310
|
+
glPostTexture = glCreateTexture(undefined);
|
|
331
311
|
glPostIncludeOverlay = includeOverlay;
|
|
332
312
|
|
|
333
313
|
// hide the original 2d canvas
|
|
@@ -363,10 +343,9 @@ function glRenderPostProcess()
|
|
|
363
343
|
|
|
364
344
|
// setup shader program to draw one triangle
|
|
365
345
|
glContext.useProgram(glPostShader);
|
|
346
|
+
glContext.bindBuffer(gl_ARRAY_BUFFER, glGeometryBuffer);
|
|
347
|
+
glContext.pixelStorei(gl_UNPACK_FLIP_Y_WEBGL, 1);
|
|
366
348
|
glContext.disable(gl_BLEND);
|
|
367
|
-
glContext.bindBuffer(gl_ARRAY_BUFFER, glPostArrayBuffer);
|
|
368
|
-
glContext.bufferData(gl_ARRAY_BUFFER, new Float32Array([-3,1,1,-3,1,1]), gl_STATIC_DRAW);
|
|
369
|
-
glContext.pixelStorei(gl_UNPACK_FLIP_Y_WEBGL, true);
|
|
370
349
|
|
|
371
350
|
// set textures, pass in the 2d canvas and gl canvas in separate texture channels
|
|
372
351
|
glContext.activeTexture(gl_TEXTURE0);
|
|
@@ -377,19 +356,19 @@ function glRenderPostProcess()
|
|
|
377
356
|
const vertexByteStride = 8;
|
|
378
357
|
const pLocation = glContext.getAttribLocation(glPostShader, 'p');
|
|
379
358
|
glContext.enableVertexAttribArray(pLocation);
|
|
380
|
-
glContext.vertexAttribPointer(pLocation, 2, gl_FLOAT,
|
|
359
|
+
glContext.vertexAttribPointer(pLocation, 2, gl_FLOAT, false, vertexByteStride, 0);
|
|
381
360
|
|
|
382
361
|
// set uniforms and draw
|
|
383
362
|
const uniformLocation = (name)=>glContext.getUniformLocation(glPostShader, name);
|
|
384
363
|
glContext.uniform1i(uniformLocation('iChannel0'), 0);
|
|
385
364
|
glContext.uniform1f(uniformLocation('iTime'), time);
|
|
386
365
|
glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
|
|
387
|
-
glContext.drawArrays(gl_TRIANGLE_STRIP, 0,
|
|
366
|
+
glContext.drawArrays(gl_TRIANGLE_STRIP, 0, 4);
|
|
388
367
|
}
|
|
389
368
|
|
|
390
369
|
///////////////////////////////////////////////////////////////////////////////
|
|
391
370
|
// store gl constants as integers so their name doesn't use space in minifed
|
|
392
|
-
const
|
|
371
|
+
const
|
|
393
372
|
gl_ONE = 1,
|
|
394
373
|
gl_TRIANGLE_STRIP = 5,
|
|
395
374
|
gl_SRC_ALPHA = 770,
|
|
@@ -411,14 +390,14 @@ gl_TEXTURE0 = 33984,
|
|
|
411
390
|
gl_ARRAY_BUFFER = 34962,
|
|
412
391
|
gl_STATIC_DRAW = 35044,
|
|
413
392
|
gl_DYNAMIC_DRAW = 35048,
|
|
414
|
-
gl_FRAGMENT_SHADER = 35632,
|
|
393
|
+
gl_FRAGMENT_SHADER = 35632,
|
|
415
394
|
gl_VERTEX_SHADER = 35633,
|
|
416
395
|
gl_COMPILE_STATUS = 35713,
|
|
417
396
|
gl_LINK_STATUS = 35714,
|
|
418
397
|
gl_UNPACK_FLIP_Y_WEBGL = 37440,
|
|
419
398
|
|
|
420
399
|
// constants for batch rendering
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
400
|
+
gl_INDICIES_PER_INSTANCE = 11,
|
|
401
|
+
gl_MAX_INSTANCES = 1e4,
|
|
402
|
+
gl_INSTANCE_BYTE_STRIDE = gl_INDICIES_PER_INSTANCE * 4, // 11 * 4
|
|
403
|
+
gl_INSTANCE_BUFFER_SIZE = gl_MAX_INSTANCES * gl_INSTANCE_BYTE_STRIDE;
|