littlejsengine 1.6.6 → 1.6.92
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 +51 -35
- package/build/littlejs.d.ts +71 -80
- package/build/littlejs.esm.js +162 -139
- package/build/littlejs.esm.min.js +1 -1
- package/build/littlejs.js +159 -129
- package/build/littlejs.min.js +1 -1
- package/build/littlejs.release.js +1942 -1907
- package/examples/breakout/game.js +5 -1
- package/examples/breakout/index.html +5 -5
- package/examples/breakoutTutorial/README.md +6 -6
- package/examples/breakoutTutorial/game.js +3 -0
- package/examples/breakoutTutorial/index.html +3 -3
- package/examples/electron/build.js +105 -0
- package/examples/electron/game.js +4 -2
- package/examples/electron/index.html +13 -2
- package/examples/electron/package.json +5 -3
- package/examples/empty/game.js +3 -1
- package/examples/empty/index.html +1 -1
- package/examples/js13k/build.bat +2 -0
- package/examples/js13k/build.js +110 -0
- package/examples/js13k/game.js +109 -0
- package/examples/js13k/index.html +18 -0
- package/examples/js13k/tiles.png +0 -0
- package/examples/module/game.js +9 -3
- package/examples/module/index.html +2 -2
- package/examples/particles/index.html +7 -13
- package/examples/platformer/game.js +5 -2
- package/examples/platformer/gameEffects.js +1 -2
- package/examples/platformer/gamePlayer.js +2 -2
- package/examples/platformer/index.html +8 -8
- package/examples/puzzle/game.js +5 -3
- package/examples/puzzle/index.html +4 -4
- package/examples/starter/build.bat +2 -78
- package/examples/starter/build.js +109 -0
- package/examples/starter/game.js +4 -1
- package/examples/starter/index.html +15 -18
- package/examples/stress/index.html +2 -4
- package/examples/typescript/build.bat +2 -14
- package/examples/typescript/build.js +31 -0
- package/examples/typescript/game.js +94 -89
- package/examples/typescript/game.ts +9 -3
- package/examples/typescript/index.html +2 -2
- package/package.json +2 -2
- package/src/engine.js +3 -3
- package/src/engineAudio.js +2 -2
- package/src/engineBuild.bat +2 -132
- package/src/engineBuild.js +143 -0
- package/src/engineDebug.js +4 -10
- package/src/engineDraw.js +12 -4
- package/src/engineExport.js +1 -8
- package/src/engineInput.js +1 -1
- package/src/engineObject.js +13 -11
- package/src/engineParticles.js +6 -6
- package/src/engineRelease.js +0 -1
- package/src/engineTileLayer.js +31 -18
- package/src/engineUtilities.js +74 -61
- package/examples/electron/build.bat +0 -52
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
10
|
let showWatermark = 0;
|
|
11
|
-
let godMode = 0;
|
|
12
11
|
let debugKeyCode = 0;
|
|
13
12
|
const debug = 0;
|
|
14
13
|
const debugOverlay = 0;
|
|
@@ -30,7 +29,7 @@ function debugLine (){}
|
|
|
30
29
|
function debugAABB (){}
|
|
31
30
|
function debugText (){}
|
|
32
31
|
function debugClear (){}
|
|
33
|
-
function debugSaveCanvas (){}
|
|
32
|
+
function debugSaveCanvas (){}
|
|
34
33
|
/**
|
|
35
34
|
* LittleJS Utility Classes and Functions
|
|
36
35
|
* - General purpose math library
|
|
@@ -52,34 +51,34 @@ const PI = Math.PI;
|
|
|
52
51
|
* @param {Number} value
|
|
53
52
|
* @return {Number}
|
|
54
53
|
* @memberof Utilities */
|
|
55
|
-
function abs(
|
|
54
|
+
function abs(value) { return Math.abs(value); }
|
|
56
55
|
|
|
57
56
|
/** Returns lowest of two values passed in
|
|
58
57
|
* @param {Number} valueA
|
|
59
58
|
* @param {Number} valueB
|
|
60
59
|
* @return {Number}
|
|
61
60
|
* @memberof Utilities */
|
|
62
|
-
function min(
|
|
61
|
+
function min(valueA, valueB) { return Math.min(valueA, valueB); }
|
|
63
62
|
|
|
64
63
|
/** Returns highest of two values passed in
|
|
65
64
|
* @param {Number} valueA
|
|
66
65
|
* @param {Number} valueB
|
|
67
66
|
* @return {Number}
|
|
68
67
|
* @memberof Utilities */
|
|
69
|
-
function max(
|
|
68
|
+
function max(valueA, valueB) { return Math.max(valueA, valueB); }
|
|
70
69
|
|
|
71
70
|
/** Returns the sign of value passed in (also returns 1 if 0)
|
|
72
71
|
* @param {Number} value
|
|
73
72
|
* @return {Number}
|
|
74
73
|
* @memberof Utilities */
|
|
75
|
-
function sign(
|
|
74
|
+
function sign(value) { return Math.sign(value); }
|
|
76
75
|
|
|
77
76
|
/** Returns first parm modulo the second param, but adjusted so negative numbers work as expected
|
|
78
77
|
* @param {Number} dividend
|
|
79
78
|
* @param {Number} [divisor=1]
|
|
80
79
|
* @return {Number}
|
|
81
80
|
* @memberof Utilities */
|
|
82
|
-
function mod(
|
|
81
|
+
function mod(dividend, divisor=1) { return ((dividend % divisor) + divisor) % divisor; }
|
|
83
82
|
|
|
84
83
|
/** Clamps the value beween max and min
|
|
85
84
|
* @param {Number} value
|
|
@@ -87,8 +86,8 @@ function mod(a, b=1) { return ((a % b) + b) % b; }
|
|
|
87
86
|
* @param {Number} [max=1]
|
|
88
87
|
* @return {Number}
|
|
89
88
|
* @memberof Utilities */
|
|
90
|
-
function clamp(
|
|
91
|
-
{ return
|
|
89
|
+
function clamp(value, min=0, max=1)
|
|
90
|
+
{ return value < min ? min : value > max ? max : value; }
|
|
92
91
|
|
|
93
92
|
/** Returns what percentage the value is between max and min
|
|
94
93
|
* @param {Number} value
|
|
@@ -96,8 +95,8 @@ function clamp(v, min=0, max=1)
|
|
|
96
95
|
* @param {Number} [max=1]
|
|
97
96
|
* @return {Number}
|
|
98
97
|
* @memberof Utilities */
|
|
99
|
-
function percent(
|
|
100
|
-
{ return max-min ? clamp((
|
|
98
|
+
function percent(value, min=0, max=1)
|
|
99
|
+
{ return max-min ? clamp((value-min) / (max-min)) : 0; }
|
|
101
100
|
|
|
102
101
|
/** Linearly interpolates the percent value between max and min
|
|
103
102
|
* @param {Number} percent
|
|
@@ -105,29 +104,32 @@ function percent(v, min=0, max=1)
|
|
|
105
104
|
* @param {Number} [max=1]
|
|
106
105
|
* @return {Number}
|
|
107
106
|
* @memberof Utilities */
|
|
108
|
-
function lerp(
|
|
107
|
+
function lerp(percent, min=0, max=1){ return min + clamp(percent) * (max-min); }
|
|
109
108
|
|
|
110
109
|
/** Applies smoothstep function to the percentage value
|
|
111
|
-
* @param {Number}
|
|
110
|
+
* @param {Number} percent
|
|
112
111
|
* @return {Number}
|
|
113
112
|
* @memberof Utilities */
|
|
114
|
-
function smoothStep(
|
|
113
|
+
function smoothStep(percent) { return percent * percent * (3 - 2 * percent); }
|
|
115
114
|
|
|
116
115
|
/** Returns the nearest power of two not less then the value
|
|
117
116
|
* @param {Number} value
|
|
118
117
|
* @return {Number}
|
|
119
118
|
* @memberof Utilities */
|
|
120
|
-
function nearestPowerOfTwo(
|
|
119
|
+
function nearestPowerOfTwo(value) { return 2**Math.ceil(Math.log2(value)); }
|
|
121
120
|
|
|
122
121
|
/** Returns true if two axis aligned bounding boxes are overlapping
|
|
123
122
|
* @param {Vector2} pointA - Center of box A
|
|
124
123
|
* @param {Vector2} sizeA - Size of box A
|
|
125
124
|
* @param {Vector2} pointB - Center of box B
|
|
126
|
-
* @param {Vector2}
|
|
125
|
+
* @param {Vector2} sizeB - Size of box B
|
|
127
126
|
* @return {Boolean} - True if overlapping
|
|
128
127
|
* @memberof Utilities */
|
|
129
|
-
function isOverlapping(
|
|
130
|
-
{
|
|
128
|
+
function isOverlapping(pointA, sizeA, pointB, sizeB)
|
|
129
|
+
{
|
|
130
|
+
return abs(pointA.x - pointB.x)*2 < sizeA.x + sizeB.x
|
|
131
|
+
&& abs(pointA.y - pointB.y)*2 < sizeA.y + sizeB.y;
|
|
132
|
+
}
|
|
131
133
|
|
|
132
134
|
/** Returns an oscillating wave between 0 and amplitude with frequency of 1 Hz by default
|
|
133
135
|
* @param {Number} [frequency=1] - Frequency of the wave in Hz
|
|
@@ -154,14 +156,14 @@ function formatTime(t) { return (t/60|0) + ':' + (t%60<10?'0':'') + (t%60|0); }
|
|
|
154
156
|
* @param {Number} [valueB=0]
|
|
155
157
|
* @return {Number}
|
|
156
158
|
* @memberof Random */
|
|
157
|
-
function rand(
|
|
159
|
+
function rand(valueA=1, valueB=0) { return valueB + Math.random() * (valueA-valueB); }
|
|
158
160
|
|
|
159
161
|
/** Returns a floored random value the two values passed in
|
|
160
162
|
* @param {Number} [valueA=1]
|
|
161
163
|
* @param {Number} [valueB=0]
|
|
162
164
|
* @return {Number}
|
|
163
165
|
* @memberof Random */
|
|
164
|
-
function randInt(
|
|
166
|
+
function randInt(valueA=1, valueB=0) { return Math.floor(rand(valueA,valueB)); }
|
|
165
167
|
|
|
166
168
|
/** Randomly returns either -1 or 1
|
|
167
169
|
* @return {Number}
|
|
@@ -188,8 +190,11 @@ function randVector(length=1) { return new Vector2().setAngle(rand(2*PI), length
|
|
|
188
190
|
* @param {Boolean} [linear]
|
|
189
191
|
* @return {Color}
|
|
190
192
|
* @memberof Random */
|
|
191
|
-
function randColor(
|
|
192
|
-
{
|
|
193
|
+
function randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear)
|
|
194
|
+
{
|
|
195
|
+
return linear ? colorA.lerp(colorB, rand()) :
|
|
196
|
+
new Color(rand(colorA.r,colorB.r), rand(colorA.g,colorB.g), rand(colorA.b,colorB.b), rand(colorA.a,colorB.a));
|
|
197
|
+
}
|
|
193
198
|
|
|
194
199
|
/** Seed used by the randSeeded function
|
|
195
200
|
* @type {Number}
|
|
@@ -207,13 +212,13 @@ function setRandSeed(seed) { randSeed = seed; }
|
|
|
207
212
|
* @param {Number} [valueB=0]
|
|
208
213
|
* @return {Number}
|
|
209
214
|
* @memberof Random */
|
|
210
|
-
function randSeeded(
|
|
215
|
+
function randSeeded(valueA=1, valueB=0)
|
|
211
216
|
{
|
|
212
217
|
// xorshift algorithm
|
|
213
218
|
randSeed ^= randSeed << 13;
|
|
214
219
|
randSeed ^= randSeed >>> 17;
|
|
215
220
|
randSeed ^= randSeed << 5;
|
|
216
|
-
return
|
|
221
|
+
return valueB + (valueA-valueB) * abs(randSeed % 1e9) / 1e9;
|
|
217
222
|
}
|
|
218
223
|
|
|
219
224
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -235,7 +240,7 @@ function vec2(x=0, y)
|
|
|
235
240
|
|
|
236
241
|
/**
|
|
237
242
|
* Check if object is a valid Vector2
|
|
238
|
-
* @param {Vector2}
|
|
243
|
+
* @param {Vector2} v
|
|
239
244
|
* @return {Boolean}
|
|
240
245
|
* @memberof Utilities
|
|
241
246
|
*/
|
|
@@ -268,27 +273,27 @@ class Vector2
|
|
|
268
273
|
copy() { return new Vector2(this.x, this.y); }
|
|
269
274
|
|
|
270
275
|
/** Returns a copy of this vector plus the vector passed in
|
|
271
|
-
* @param {Vector2} vector
|
|
276
|
+
* @param {Vector2} v - other vector
|
|
272
277
|
* @return {Vector2} */
|
|
273
278
|
add(v) { ASSERT(isVector2(v)); return new Vector2(this.x + v.x, this.y + v.y); }
|
|
274
279
|
|
|
275
280
|
/** Returns a copy of this vector minus the vector passed in
|
|
276
|
-
* @param {Vector2} vector
|
|
281
|
+
* @param {Vector2} v - other vector
|
|
277
282
|
* @return {Vector2} */
|
|
278
283
|
subtract(v) { ASSERT(isVector2(v)); return new Vector2(this.x - v.x, this.y - v.y); }
|
|
279
284
|
|
|
280
285
|
/** Returns a copy of this vector times the vector passed in
|
|
281
|
-
* @param {Vector2} vector
|
|
286
|
+
* @param {Vector2} v - other vector
|
|
282
287
|
* @return {Vector2} */
|
|
283
288
|
multiply(v) { ASSERT(isVector2(v)); return new Vector2(this.x * v.x, this.y * v.y); }
|
|
284
289
|
|
|
285
290
|
/** Returns a copy of this vector divided by the vector passed in
|
|
286
|
-
* @param {Vector2} vector
|
|
291
|
+
* @param {Vector2} v - other vector
|
|
287
292
|
* @return {Vector2} */
|
|
288
293
|
divide(v) { ASSERT(isVector2(v)); return new Vector2(this.x / v.x, this.y / v.y); }
|
|
289
294
|
|
|
290
295
|
/** Returns a copy of this vector scaled by the vector passed in
|
|
291
|
-
* @param {Number} scale
|
|
296
|
+
* @param {Number} s - scale
|
|
292
297
|
* @return {Vector2} */
|
|
293
298
|
scale(s) { ASSERT(!isVector2(s)); return new Vector2(this.x * s, this.y * s); }
|
|
294
299
|
|
|
@@ -301,12 +306,12 @@ class Vector2
|
|
|
301
306
|
lengthSquared() { return this.x**2 + this.y**2; }
|
|
302
307
|
|
|
303
308
|
/** Returns the distance from this vector to vector passed in
|
|
304
|
-
* @param {Vector2} vector
|
|
309
|
+
* @param {Vector2} v - other vector
|
|
305
310
|
* @return {Number} */
|
|
306
311
|
distance(v) { return this.distanceSquared(v)**.5; }
|
|
307
312
|
|
|
308
313
|
/** Returns the distance squared from this vector to vector passed in
|
|
309
|
-
* @param {Vector2} vector
|
|
314
|
+
* @param {Vector2} v - other vector
|
|
310
315
|
* @return {Number} */
|
|
311
316
|
distanceSquared(v) { return (this.x - v.x)**2 + (this.y - v.y)**2; }
|
|
312
317
|
|
|
@@ -321,12 +326,12 @@ class Vector2
|
|
|
321
326
|
clampLength(length=1) { const l = this.length(); return l > length ? this.scale(length/l) : this; }
|
|
322
327
|
|
|
323
328
|
/** Returns the dot product of this and the vector passed in
|
|
324
|
-
* @param {Vector2} vector
|
|
329
|
+
* @param {Vector2} v - other vector
|
|
325
330
|
* @return {Number} */
|
|
326
331
|
dot(v) { ASSERT(isVector2(v)); return this.x*v.x + this.y*v.y; }
|
|
327
332
|
|
|
328
333
|
/** Returns the cross product of this and the vector passed in
|
|
329
|
-
* @param {Vector2} vector
|
|
334
|
+
* @param {Vector2} v - other vector
|
|
330
335
|
* @return {Number} */
|
|
331
336
|
cross(v) { ASSERT(isVector2(v)); return this.x*v.y - this.y*v.x; }
|
|
332
337
|
|
|
@@ -338,12 +343,17 @@ class Vector2
|
|
|
338
343
|
* @param {Number} [angle=0]
|
|
339
344
|
* @param {Number} [length=1]
|
|
340
345
|
* @return {Vector2} */
|
|
341
|
-
setAngle(
|
|
346
|
+
setAngle(angle=0, length=1)
|
|
347
|
+
{ this.x = length*Math.sin(angle); this.y = length*Math.cos(angle); return this; }
|
|
342
348
|
|
|
343
349
|
/** Returns copy of this vector rotated by the angle passed in
|
|
344
350
|
* @param {Number} angle
|
|
345
351
|
* @return {Vector2} */
|
|
346
|
-
rotate(
|
|
352
|
+
rotate(angle)
|
|
353
|
+
{
|
|
354
|
+
const c = Math.cos(angle), s = Math.sin(angle);
|
|
355
|
+
return new Vector2(this.x*c - this.y*s, this.x*s + this.y*c);
|
|
356
|
+
}
|
|
347
357
|
|
|
348
358
|
/** Returns the integer direction of this vector, corrosponding to multiples of 90 degree rotation (0-3)
|
|
349
359
|
* @return {Number} */
|
|
@@ -362,10 +372,11 @@ class Vector2
|
|
|
362
372
|
area() { return abs(this.x * this.y); }
|
|
363
373
|
|
|
364
374
|
/** Returns a new vector that is p percent between this and the vector passed in
|
|
365
|
-
* @param {Vector2} vector
|
|
375
|
+
* @param {Vector2} v - other vector
|
|
366
376
|
* @param {Number} percent
|
|
367
377
|
* @return {Vector2} */
|
|
368
|
-
lerp(v,
|
|
378
|
+
lerp(v, percent)
|
|
379
|
+
{ ASSERT(isVector2(v)); return this.add(v.subtract(this).scale(clamp(percent))); }
|
|
369
380
|
|
|
370
381
|
/** Returns true if this vector is within the bounds of an array size passed in
|
|
371
382
|
* @param {Vector2} arraySize
|
|
@@ -383,10 +394,10 @@ class Vector2
|
|
|
383
394
|
|
|
384
395
|
/**
|
|
385
396
|
* Create a color object with RGBA values
|
|
386
|
-
* @param {Number} [r=1]
|
|
387
|
-
* @param {Number} [g=1]
|
|
388
|
-
* @param {Number} [b=1]
|
|
389
|
-
* @param {Number} [a=1]
|
|
397
|
+
* @param {Number} [r=1] - red
|
|
398
|
+
* @param {Number} [g=1] - green
|
|
399
|
+
* @param {Number} [b=1] - blue
|
|
400
|
+
* @param {Number} [a=1] - alpha
|
|
390
401
|
* @return {Color}
|
|
391
402
|
* @memberof Utilities
|
|
392
403
|
*/
|
|
@@ -394,10 +405,10 @@ function rgb(r, g, b, a) { return new Color(r, g, b, a); }
|
|
|
394
405
|
|
|
395
406
|
/**
|
|
396
407
|
* Create a color object with HSLA values
|
|
397
|
-
* @param {Number} [h=0]
|
|
398
|
-
* @param {Number} [s=0]
|
|
399
|
-
* @param {Number} [l=1]
|
|
400
|
-
* @param {Number} [a=1]
|
|
408
|
+
* @param {Number} [h=0] - hue
|
|
409
|
+
* @param {Number} [s=0] - saturation
|
|
410
|
+
* @param {Number} [l=1] - lightness
|
|
411
|
+
* @param {Number} [a=1] - alpha
|
|
401
412
|
* @return {Color}
|
|
402
413
|
* @memberof Utilities
|
|
403
414
|
*/
|
|
@@ -414,11 +425,11 @@ function hsl(h, s, l, a) { return new Color().setHSLA(h, s, l, a); }
|
|
|
414
425
|
*/
|
|
415
426
|
class Color
|
|
416
427
|
{
|
|
417
|
-
/** Create a color with the components passed in, white by default
|
|
418
|
-
* @param {Number} [
|
|
419
|
-
* @param {Number} [
|
|
420
|
-
* @param {Number} [
|
|
421
|
-
* @param {Number} [
|
|
428
|
+
/** Create a color with the rgba components passed in, white by default
|
|
429
|
+
* @param {Number} [r=1] - red
|
|
430
|
+
* @param {Number} [g=1] - green
|
|
431
|
+
* @param {Number} [b=1] - blue
|
|
432
|
+
* @param {Number} [a=1] - alpha*/
|
|
422
433
|
constructor(r=1, g=1, b=1, a=1)
|
|
423
434
|
{
|
|
424
435
|
/** @property {Number} - Red */
|
|
@@ -436,22 +447,22 @@ class Color
|
|
|
436
447
|
copy() { return new Color(this.r, this.g, this.b, this.a); }
|
|
437
448
|
|
|
438
449
|
/** Returns a copy of this color plus the color passed in
|
|
439
|
-
* @param {Color} color
|
|
450
|
+
* @param {Color} c - other color
|
|
440
451
|
* @return {Color} */
|
|
441
452
|
add(c) { return new Color(this.r+c.r, this.g+c.g, this.b+c.b, this.a+c.a); }
|
|
442
453
|
|
|
443
454
|
/** Returns a copy of this color minus the color passed in
|
|
444
|
-
* @param {Color} color
|
|
455
|
+
* @param {Color} c - other color
|
|
445
456
|
* @return {Color} */
|
|
446
457
|
subtract(c) { return new Color(this.r-c.r, this.g-c.g, this.b-c.b, this.a-c.a); }
|
|
447
458
|
|
|
448
459
|
/** Returns a copy of this color times the color passed in
|
|
449
|
-
* @param {Color} color
|
|
460
|
+
* @param {Color} c - other color
|
|
450
461
|
* @return {Color} */
|
|
451
462
|
multiply(c) { return new Color(this.r*c.r, this.g*c.g, this.b*c.b, this.a*c.a); }
|
|
452
463
|
|
|
453
464
|
/** Returns a copy of this color divided by the color passed in
|
|
454
|
-
* @param {Color} color
|
|
465
|
+
* @param {Color} c - other color
|
|
455
466
|
* @return {Color} */
|
|
456
467
|
divide(c) { return new Color(this.r/c.r, this.g/c.g, this.b/c.b, this.a/c.a); }
|
|
457
468
|
|
|
@@ -459,23 +470,24 @@ class Color
|
|
|
459
470
|
* @param {Number} scale
|
|
460
471
|
* @param {Number} [alphaScale=scale]
|
|
461
472
|
* @return {Color} */
|
|
462
|
-
scale(
|
|
473
|
+
scale(scale, alphaScale=scale)
|
|
474
|
+
{ return new Color(this.r*scale, this.g*scale, this.b*scale, this.a*alphaScale); }
|
|
463
475
|
|
|
464
476
|
/** Returns a copy of this color clamped to the valid range between 0 and 1
|
|
465
477
|
* @return {Color} */
|
|
466
478
|
clamp() { return new Color(clamp(this.r), clamp(this.g), clamp(this.b), clamp(this.a)); }
|
|
467
479
|
|
|
468
480
|
/** Returns a new color that is p percent between this and the color passed in
|
|
469
|
-
* @param {Color} color
|
|
481
|
+
* @param {Color} c - other color
|
|
470
482
|
* @param {Number} percent
|
|
471
483
|
* @return {Color} */
|
|
472
|
-
lerp(c,
|
|
484
|
+
lerp(c, percent) { return this.add(c.subtract(this).scale(clamp(percent))); }
|
|
473
485
|
|
|
474
486
|
/** Sets this color given a hue, saturation, lightness, and alpha
|
|
475
|
-
* @param {Number} [
|
|
476
|
-
* @param {Number} [
|
|
477
|
-
* @param {Number} [
|
|
478
|
-
* @param {Number} [
|
|
487
|
+
* @param {Number} [h=0] - hue
|
|
488
|
+
* @param {Number} [s=0] - saturation
|
|
489
|
+
* @param {Number} [l=1] - lightness
|
|
490
|
+
* @param {Number} [a=1] - alpha
|
|
479
491
|
* @return {Color} */
|
|
480
492
|
setHSLA(h=0, s=0, l=1, a=1)
|
|
481
493
|
{
|
|
@@ -616,12 +628,12 @@ class Timer
|
|
|
616
628
|
|
|
617
629
|
/** Returns this timer expressed as a string
|
|
618
630
|
* @return {String} */
|
|
619
|
-
toString() { if (debug) { return this.
|
|
631
|
+
toString() { if (debug) { return this.isSet() ? Math.abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; }}
|
|
620
632
|
|
|
621
633
|
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
622
634
|
* @return {Number} */
|
|
623
635
|
valueOf() { return this.get(); }
|
|
624
|
-
}
|
|
636
|
+
}
|
|
625
637
|
/**
|
|
626
638
|
* LittleJS Engine Settings
|
|
627
639
|
* @namespace Settings
|
|
@@ -870,772 +882,456 @@ let medalDisplayIconSize = 50;
|
|
|
870
882
|
* @type {Boolean}
|
|
871
883
|
* @default 0
|
|
872
884
|
* @memberof Settings */
|
|
873
|
-
let medalsPreventUnlock;
|
|
885
|
+
let medalsPreventUnlock;
|
|
874
886
|
/**
|
|
875
|
-
* LittleJS
|
|
876
|
-
* MIT License - Copyright 2021 Frank Force
|
|
877
|
-
*
|
|
878
|
-
* Engine Features
|
|
879
|
-
* - Object oriented system with base class engine object
|
|
880
|
-
* - Base class object handles update, physics, collision, rendering, etc
|
|
881
|
-
* - Engine helper classes and functions like Vector2, Color, and Timer
|
|
882
|
-
* - Super fast rendering system for tile sheets
|
|
883
|
-
* - Sound effects audio with zzfx and music with zzfxm
|
|
884
|
-
* - Input processing system with gamepad and touchscreen support
|
|
885
|
-
* - Tile layer rendering and collision system
|
|
886
|
-
* - Particle effect system
|
|
887
|
-
* - Medal system tracks and displays achievements
|
|
888
|
-
* - Debug tools and debug rendering system
|
|
889
|
-
* - Post processing effects
|
|
890
|
-
* - Call engineInit() to start it up!
|
|
891
|
-
* @namespace Engine
|
|
887
|
+
* LittleJS Object System
|
|
892
888
|
*/
|
|
893
889
|
|
|
894
890
|
'use strict';
|
|
895
891
|
|
|
896
|
-
/**
|
|
897
|
-
*
|
|
898
|
-
*
|
|
899
|
-
*
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
*
|
|
904
|
-
*
|
|
905
|
-
*
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
*
|
|
910
|
-
*
|
|
911
|
-
*
|
|
912
|
-
|
|
892
|
+
/**
|
|
893
|
+
* LittleJS Object Base Object Class
|
|
894
|
+
* - Base object class used by the engine
|
|
895
|
+
* - Automatically adds self to object list
|
|
896
|
+
* - Will be updated and rendered each frame
|
|
897
|
+
* - Renders as a sprite from a tilesheet by default
|
|
898
|
+
* - Can have color and addtive color applied
|
|
899
|
+
* - 2d Physics and collision system
|
|
900
|
+
* - Sorted by renderOrder
|
|
901
|
+
* - Objects can have children attached
|
|
902
|
+
* - Parents are updated before children, and set child transform
|
|
903
|
+
* - Call destroy() to get rid of objects
|
|
904
|
+
*
|
|
905
|
+
* The physics system used by objects is simple and fast with some caveats...
|
|
906
|
+
* - Collision uses the axis aligned size, the object's rotation angle is only for rendering
|
|
907
|
+
* - Objects are guaranteed to not intersect tile collision from physics
|
|
908
|
+
* - If an object starts or is moved inside tile collision, it will not collide with that tile
|
|
909
|
+
* - Collision for objects can be set to be solid to block other objects
|
|
910
|
+
* - Objects may get pushed into overlapping other solid objects, if so they will push away
|
|
911
|
+
* - Solid objects are more performance intensive and should be used sparingly
|
|
912
|
+
* @example
|
|
913
|
+
* // create an engine object, normally you would first extend the class with your own
|
|
914
|
+
* const pos = vec2(2,3);
|
|
915
|
+
* const object = new EngineObject(pos);
|
|
916
|
+
*/
|
|
917
|
+
class EngineObject
|
|
918
|
+
{
|
|
919
|
+
/** Create an engine object and adds it to the list of objects
|
|
920
|
+
* @param {Vector2} [position=Vector2()] - World space position of the object
|
|
921
|
+
* @param {Vector2} [size=Vector2(1,1)] - World space size of the object
|
|
922
|
+
* @param {Number} [tileIndex=-1] - Tile to use to render object (-1 is untextured)
|
|
923
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
|
|
924
|
+
* @param {Number} [angle=0] - Angle the object is rotated by
|
|
925
|
+
* @param {Color} [color=Color()] - Color to apply to tile when rendered
|
|
926
|
+
* @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
|
|
927
|
+
*/
|
|
928
|
+
constructor(pos=vec2(), size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, angle=0, color, renderOrder=0)
|
|
929
|
+
{
|
|
930
|
+
// set passed in params
|
|
931
|
+
ASSERT(isVector2(pos) && isVector2(size)); // ensure pos and size are vec2s
|
|
913
932
|
|
|
914
|
-
/**
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
933
|
+
/** @property {Vector2} - World space position of the object */
|
|
934
|
+
this.pos = pos.copy();
|
|
935
|
+
/** @property {Vector2} - World space width and height of the object */
|
|
936
|
+
this.size = size;
|
|
937
|
+
/** @property {Vector2} - Size of object used for drawing, uses size if not set */
|
|
938
|
+
this.drawSize;
|
|
939
|
+
/** @property {Number} - Tile to use to render object (-1 is untextured) */
|
|
940
|
+
this.tileIndex = tileIndex;
|
|
941
|
+
/** @property {Vector2} - Size of tile in source pixels */
|
|
942
|
+
this.tileSize = tileSize;
|
|
943
|
+
/** @property {Number} - Angle to rotate the object */
|
|
944
|
+
this.angle = angle;
|
|
945
|
+
/** @property {Color} - Color to apply when rendered */
|
|
946
|
+
this.color = color;
|
|
947
|
+
/** @property {Color} - Additive color to apply when rendered */
|
|
948
|
+
this.additiveColor;
|
|
919
949
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
950
|
+
// set object defaults
|
|
951
|
+
/** @property {Number} [mass=objectDefaultMass] - How heavy the object is, static if 0 */
|
|
952
|
+
this.mass = objectDefaultMass;
|
|
953
|
+
/** @property {Number} [damping=objectDefaultDamping] - How much to slow down velocity each frame (0-1) */
|
|
954
|
+
this.damping = objectDefaultDamping;
|
|
955
|
+
/** @property {Number} [angleDamping=objectDefaultAngleDamping] - How much to slow down rotation each frame (0-1) */
|
|
956
|
+
this.angleDamping = objectDefaultAngleDamping;
|
|
957
|
+
/** @property {Number} [elasticity=objectDefaultElasticity] - How bouncy the object is when colliding (0-1) */
|
|
958
|
+
this.elasticity = objectDefaultElasticity;
|
|
959
|
+
/** @property {Number} [friction=objectDefaultFriction] - How much friction to apply when sliding (0-1) */
|
|
960
|
+
this.friction = objectDefaultFriction;
|
|
961
|
+
/** @property {Number} [gravityScale=1] - How much to scale gravity by for this object */
|
|
962
|
+
this.gravityScale = 1;
|
|
963
|
+
/** @property {Number} [renderOrder=0] - Objects are sorted by render order */
|
|
964
|
+
this.renderOrder = renderOrder;
|
|
965
|
+
/** @property {Vector2} [velocity=Vector2()] - Velocity of the object */
|
|
966
|
+
this.velocity = vec2();
|
|
967
|
+
/** @property {Number} [angleVelocity=0] - Angular velocity of the object */
|
|
968
|
+
this.angleVelocity = 0;
|
|
924
969
|
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
970
|
+
// init other internal object stuff
|
|
971
|
+
this.spawnTime = time;
|
|
972
|
+
this.children = [];
|
|
973
|
+
this.collideTiles = 1;
|
|
929
974
|
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
975
|
+
// add to list of objects
|
|
976
|
+
engineObjects.push(this);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
/** Update the object transform and physics, called automatically by engine once each frame */
|
|
980
|
+
update()
|
|
981
|
+
{
|
|
982
|
+
const parent = this.parent;
|
|
983
|
+
if (parent)
|
|
984
|
+
{
|
|
985
|
+
// copy parent pos/angle
|
|
986
|
+
this.pos = this.localPos.multiply(vec2(parent.getMirrorSign(),1)).rotate(-parent.angle).add(parent.pos);
|
|
987
|
+
this.angle = parent.getMirrorSign()*this.localAngle + parent.angle;
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
934
990
|
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
let time = 0;
|
|
991
|
+
// limit max speed to prevent missing collisions
|
|
992
|
+
this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
|
|
993
|
+
this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
|
|
939
994
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
*
|
|
943
|
-
|
|
995
|
+
// apply physics
|
|
996
|
+
const oldPos = this.pos.copy();
|
|
997
|
+
this.velocity.y += gravity * this.gravityScale;
|
|
998
|
+
this.pos.x += this.velocity.x *= this.damping;
|
|
999
|
+
this.pos.y += this.velocity.y *= this.damping;
|
|
1000
|
+
this.angle += this.angleVelocity *= this.angleDamping;
|
|
944
1001
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
* @memberof Engine */
|
|
949
|
-
let paused = 0;
|
|
1002
|
+
// physics sanity checks
|
|
1003
|
+
ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
|
|
1004
|
+
ASSERT(this.damping >= 0 && this.damping <= 1);
|
|
950
1005
|
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
* @memberof Engine */
|
|
954
|
-
function setPaused(_paused) { paused = _paused; }
|
|
1006
|
+
if (!enablePhysicsSolver || !this.mass) // do not update collision for fixed objects
|
|
1007
|
+
return;
|
|
955
1008
|
|
|
956
|
-
|
|
1009
|
+
const wasMovingDown = this.velocity.y < 0;
|
|
1010
|
+
if (this.groundObject)
|
|
1011
|
+
{
|
|
1012
|
+
// apply friction in local space of ground object
|
|
1013
|
+
const groundSpeed = this.groundObject.velocity ? this.groundObject.velocity.x : 0;
|
|
1014
|
+
this.velocity.x = groundSpeed + (this.velocity.x - groundSpeed) * this.friction;
|
|
1015
|
+
this.groundObject = 0;
|
|
1016
|
+
//debugOverlay && debugPhysics && debugPoint(this.pos.subtract(vec2(0,this.size.y/2)), '#0f0');
|
|
1017
|
+
}
|
|
957
1018
|
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
{
|
|
968
|
-
// init engine when tiles load or fail to load
|
|
969
|
-
tileImage.onerror = tileImage.onload = ()=>
|
|
970
|
-
{
|
|
971
|
-
// save tile image info
|
|
972
|
-
tileImageFixBleed = vec2(tileFixBleedScale).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
|
|
973
|
-
debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
|
|
1019
|
+
if (this.collideSolidObjects)
|
|
1020
|
+
{
|
|
1021
|
+
// check collisions against solid objects
|
|
1022
|
+
const epsilon = .001; // necessary to push slightly outside of the collision
|
|
1023
|
+
for (const o of engineObjectsCollide)
|
|
1024
|
+
{
|
|
1025
|
+
// non solid objects don't collide with eachother
|
|
1026
|
+
if (!this.isSolid && !o.isSolid || o.destroyed || o.parent || o == this)
|
|
1027
|
+
continue;
|
|
974
1028
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
'touch-action:none;' + // prevent mobile pinch to resize
|
|
979
|
-
'user-select:none;' + // prevent mobile hold to select
|
|
980
|
-
'-webkit-user-select:none'; // compatibility for ios
|
|
981
|
-
document.body.style = styleBody;
|
|
982
|
-
document.body.appendChild(mainCanvas = document.createElement('canvas'));
|
|
983
|
-
mainContext = mainCanvas.getContext('2d');
|
|
1029
|
+
// check collision
|
|
1030
|
+
if (!isOverlapping(this.pos, this.size, o.pos, o.size))
|
|
1031
|
+
continue;
|
|
984
1032
|
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1033
|
+
// notify objects of collision and check if should be resolved
|
|
1034
|
+
const collide1 = this.collideWithObject(o);
|
|
1035
|
+
const collide2 = o.collideWithObject(this);
|
|
1036
|
+
if (!collide1 || !collide2)
|
|
1037
|
+
continue;
|
|
988
1038
|
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1039
|
+
if (isOverlapping(oldPos, this.size, o.pos, o.size))
|
|
1040
|
+
{
|
|
1041
|
+
// if already was touching, try to push away
|
|
1042
|
+
const deltaPos = oldPos.subtract(o.pos);
|
|
1043
|
+
const length = deltaPos.length();
|
|
1044
|
+
const pushAwayAccel = .001; // push away if already overlapping
|
|
1045
|
+
const velocity = length < .01 ? randVector(pushAwayAccel) : deltaPos.scale(pushAwayAccel/length);
|
|
1046
|
+
this.velocity = this.velocity.add(velocity);
|
|
1047
|
+
if (o.mass) // push away if not fixed
|
|
1048
|
+
o.velocity = o.velocity.subtract(velocity);
|
|
1049
|
+
|
|
1050
|
+
debugOverlay && debugPhysics && debugAABB(this.pos, this.size, o.pos, o.size, '#f00');
|
|
1051
|
+
continue;
|
|
1052
|
+
}
|
|
992
1053
|
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1054
|
+
// check for collision
|
|
1055
|
+
const sizeBoth = this.size.add(o.size);
|
|
1056
|
+
const smallStepUp = (oldPos.y - o.pos.y)*2 > sizeBoth.y + gravity; // prefer to push up if small delta
|
|
1057
|
+
const isBlockedX = abs(oldPos.y - o.pos.y)*2 < sizeBoth.y;
|
|
1058
|
+
const isBlockedY = abs(oldPos.x - o.pos.x)*2 < sizeBoth.x;
|
|
1059
|
+
const elasticity = max(this.elasticity, o.elasticity);
|
|
1060
|
+
|
|
1061
|
+
if (smallStepUp || isBlockedY || !isBlockedX) // resolve y collision
|
|
1062
|
+
{
|
|
1063
|
+
// push outside object collision
|
|
1064
|
+
this.pos.y = o.pos.y + (sizeBoth.y/2 + epsilon) * sign(oldPos.y - o.pos.y);
|
|
1065
|
+
if (o.groundObject && wasMovingDown || !o.mass)
|
|
1066
|
+
{
|
|
1067
|
+
// set ground object if landed on something
|
|
1068
|
+
if (wasMovingDown)
|
|
1069
|
+
this.groundObject = o;
|
|
1002
1070
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1071
|
+
// bounce if other object is fixed or grounded
|
|
1072
|
+
this.velocity.y *= -elasticity;
|
|
1073
|
+
}
|
|
1074
|
+
else if (o.mass)
|
|
1075
|
+
{
|
|
1076
|
+
// inelastic collision
|
|
1077
|
+
const inelastic = (this.mass * this.velocity.y + o.mass * o.velocity.y) / (this.mass + o.mass);
|
|
1005
1078
|
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
frameTimeLastMS = frameTimeMS;
|
|
1012
|
-
if (debug || showWatermark)
|
|
1013
|
-
averageFPS = lerp(.05, averageFPS, 1e3/(frameTimeDeltaMS||1));
|
|
1014
|
-
const debugSpeedUp = debug && keyIsDown(107); // +
|
|
1015
|
-
const debugSpeedDown = debug && keyIsDown(109); // -
|
|
1016
|
-
if (debug) // +/- to speed/slow time
|
|
1017
|
-
frameTimeDeltaMS *= debugSpeedUp ? 5 : debugSpeedDown ? .2 : 1;
|
|
1018
|
-
timeReal += frameTimeDeltaMS / 1e3;
|
|
1019
|
-
frameTimeBufferMS += !paused * frameTimeDeltaMS;
|
|
1020
|
-
if (!debugSpeedUp)
|
|
1021
|
-
frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp incase of slow framerate
|
|
1079
|
+
// elastic collision
|
|
1080
|
+
const elastic0 = this.velocity.y * (this.mass - o.mass) / (this.mass + o.mass)
|
|
1081
|
+
+ o.velocity.y * 2 * o.mass / (this.mass + o.mass);
|
|
1082
|
+
const elastic1 = o.velocity.y * (o.mass - this.mass) / (this.mass + o.mass)
|
|
1083
|
+
+ this.velocity.y * 2 * this.mass / (this.mass + o.mass);
|
|
1022
1084
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
{
|
|
1037
|
-
// clear canvas and set size to same as window
|
|
1038
|
-
mainCanvas.width = min(innerWidth, canvasMaxSize.x);
|
|
1039
|
-
mainCanvas.height = min(innerHeight, canvasMaxSize.y);
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
// clear overlay canvas and set size
|
|
1043
|
-
overlayCanvas.width = mainCanvas.width;
|
|
1044
|
-
overlayCanvas.height = mainCanvas.height;
|
|
1085
|
+
// lerp betwen elastic or inelastic based on elasticity
|
|
1086
|
+
this.velocity.y = lerp(elasticity, inelastic, elastic0);
|
|
1087
|
+
o.velocity.y = lerp(elasticity, inelastic, elastic1);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
if (!smallStepUp && isBlockedX) // resolve x collision
|
|
1091
|
+
{
|
|
1092
|
+
// push outside collision
|
|
1093
|
+
this.pos.x = o.pos.x + (sizeBoth.x/2 + epsilon) * sign(oldPos.x - o.pos.x);
|
|
1094
|
+
if (o.mass)
|
|
1095
|
+
{
|
|
1096
|
+
// inelastic collision
|
|
1097
|
+
const inelastic = (this.mass * this.velocity.x + o.mass * o.velocity.x) / (this.mass + o.mass);
|
|
1045
1098
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1099
|
+
// elastic collision
|
|
1100
|
+
const elastic0 = this.velocity.x * (this.mass - o.mass) / (this.mass + o.mass)
|
|
1101
|
+
+ o.velocity.x * 2 * o.mass / (this.mass + o.mass);
|
|
1102
|
+
const elastic1 = o.velocity.x * (o.mass - this.mass) / (this.mass + o.mass)
|
|
1103
|
+
+ this.velocity.x * 2 * this.mass / (this.mass + o.mass);
|
|
1048
1104
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1105
|
+
// lerp betwen elastic or inelastic based on elasticity
|
|
1106
|
+
this.velocity.x = lerp(elasticity, inelastic, elastic0);
|
|
1107
|
+
o.velocity.x = lerp(elasticity, inelastic, elastic1);
|
|
1108
|
+
}
|
|
1109
|
+
else // bounce if other object is fixed
|
|
1110
|
+
this.velocity.x *= -elasticity;
|
|
1111
|
+
}
|
|
1112
|
+
debugOverlay && debugPhysics && debugAABB(this.pos, this.size, o.pos, o.size, '#f0f');
|
|
1113
|
+
}
|
|
1056
1114
|
}
|
|
1057
|
-
|
|
1115
|
+
if (this.collideTiles)
|
|
1058
1116
|
{
|
|
1059
|
-
//
|
|
1060
|
-
|
|
1061
|
-
if (frameTimeBufferMS < 0 && frameTimeBufferMS > -9)
|
|
1062
|
-
{
|
|
1063
|
-
// force an update each frame if time is close enough (not just a fast refresh rate)
|
|
1064
|
-
deltaSmooth = frameTimeBufferMS;
|
|
1065
|
-
frameTimeBufferMS = 0;
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
// update multiple frames if necessary in case of slow framerate
|
|
1069
|
-
for (;frameTimeBufferMS >= 0; frameTimeBufferMS -= 1e3 / frameRate)
|
|
1117
|
+
// check collision against tiles
|
|
1118
|
+
if (tileCollisionTest(this.pos, this.size, this))
|
|
1070
1119
|
{
|
|
1071
|
-
//
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1120
|
+
// if already was stuck in collision, don't do anything
|
|
1121
|
+
// this should not happen unless something starts in collision
|
|
1122
|
+
if (!tileCollisionTest(oldPos, this.size, this))
|
|
1123
|
+
{
|
|
1124
|
+
// test which side we bounced off (or both if a corner)
|
|
1125
|
+
const isBlockedY = tileCollisionTest(vec2(oldPos.x, this.pos.y), this.size, this);
|
|
1126
|
+
const isBlockedX = tileCollisionTest(vec2(this.pos.x, oldPos.y), this.size, this);
|
|
1127
|
+
if (isBlockedY || !isBlockedX)
|
|
1128
|
+
{
|
|
1129
|
+
// set if landed on ground
|
|
1130
|
+
this.groundObject = wasMovingDown;
|
|
1075
1131
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
gameUpdatePost();
|
|
1079
|
-
inputUpdatePost();
|
|
1080
|
-
}
|
|
1132
|
+
// bounce velocity
|
|
1133
|
+
this.velocity.y *= -this.elasticity;
|
|
1081
1134
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
// render sort then render while removing destroyed objects
|
|
1087
|
-
enginePreRender();
|
|
1088
|
-
gameRender();
|
|
1089
|
-
engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
|
|
1090
|
-
for (const o of engineObjects)
|
|
1091
|
-
o.destroyed || o.render();
|
|
1092
|
-
gameRenderPost();
|
|
1093
|
-
glRenderPostProcess();
|
|
1094
|
-
medalsRender();
|
|
1095
|
-
touchGamepadRender();
|
|
1096
|
-
debugRender();
|
|
1097
|
-
glEnable && glCopyToContext(mainContext);
|
|
1135
|
+
// adjust next velocity to settle on ground
|
|
1136
|
+
const o = (oldPos.y - this.size.y/2|0) - (oldPos.y - this.size.y/2);
|
|
1137
|
+
if (o < 0 && o > this.damping * this.velocity.y + gravity * this.gravityScale)
|
|
1138
|
+
this.velocity.y = this.damping ? (o - gravity * this.gravityScale) / this.damping : 0;
|
|
1098
1139
|
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
overlayContext.fillStyle = '#fff';
|
|
1111
|
-
overlayContext.fillText(text, mainCanvas.width-2, 2);
|
|
1112
|
-
drawCount = 0;
|
|
1140
|
+
// move to previous position
|
|
1141
|
+
this.pos.y = oldPos.y;
|
|
1142
|
+
}
|
|
1143
|
+
if (isBlockedX)
|
|
1144
|
+
{
|
|
1145
|
+
// move to previous position and bounce
|
|
1146
|
+
this.pos.x = oldPos.x;
|
|
1147
|
+
this.velocity.x *= -this.elasticity;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1113
1151
|
}
|
|
1114
|
-
|
|
1115
|
-
requestAnimationFrame(engineUpdate);
|
|
1116
1152
|
}
|
|
1153
|
+
|
|
1154
|
+
/** Render the object, draws a tile by default, automatically called each frame, sorted by renderOrder */
|
|
1155
|
+
render()
|
|
1156
|
+
{
|
|
1157
|
+
// default object render
|
|
1158
|
+
drawTile(this.pos, this.drawSize || this.size, this.tileIndex, this.tileSize, this.color, this.angle, this.mirror, this.additiveColor);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
/** Destroy this object, destroy it's children, detach it's parent, and mark it for removal */
|
|
1162
|
+
destroy()
|
|
1163
|
+
{
|
|
1164
|
+
if (this.destroyed)
|
|
1165
|
+
return;
|
|
1166
|
+
|
|
1167
|
+
// disconnect from parent and destroy chidren
|
|
1168
|
+
this.destroyed = 1;
|
|
1169
|
+
this.parent && this.parent.removeChild(this);
|
|
1170
|
+
for (const child of this.children)
|
|
1171
|
+
child.destroy(child.parent = 0);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
/** Called to check if a tile collision should be resolved
|
|
1175
|
+
* @param {Number} tileData - the value of the tile at the position
|
|
1176
|
+
* @param {Vector2} pos - tile where the collision occured
|
|
1177
|
+
* @return {Boolean} - true if the collision should be resolved */
|
|
1178
|
+
collideWithTile(tileData, pos) { return tileData > 0; }
|
|
1179
|
+
|
|
1180
|
+
/** Called to check if a tile raycast hit
|
|
1181
|
+
* @param {Number} tileData - the value of the tile at the position
|
|
1182
|
+
* @param {Vector2} pos - tile where the raycast is
|
|
1183
|
+
* @return {Boolean} - true if the raycast should hit */
|
|
1184
|
+
collideWithTileRaycast(tileData, pos) { return tileData > 0; }
|
|
1117
1185
|
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
function enginePreRender()
|
|
1124
|
-
{
|
|
1125
|
-
// save canvas size
|
|
1126
|
-
mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
|
|
1186
|
+
/** Called to check if a object collision should be resolved
|
|
1187
|
+
* @param {EngineObject} object - the object to test against
|
|
1188
|
+
* @return {Boolean} - true if the collision should be resolved
|
|
1189
|
+
*/
|
|
1190
|
+
collideWithObject(object) { return 1; }
|
|
1127
1191
|
|
|
1128
|
-
|
|
1129
|
-
|
|
1192
|
+
/** How long since the object was created
|
|
1193
|
+
* @return {Number} */
|
|
1194
|
+
getAliveTime() { return time - this.spawnTime; }
|
|
1130
1195
|
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
}
|
|
1196
|
+
/** Apply acceleration to this object (adjust velocity, not affected by mass)
|
|
1197
|
+
* @param {Vector2} acceleration */
|
|
1198
|
+
applyAcceleration(acceleration) { if (this.mass) this.velocity = this.velocity.add(acceleration); }
|
|
1134
1199
|
|
|
1135
|
-
/**
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1200
|
+
/** Apply force to this object (adjust velocity, affected by mass)
|
|
1201
|
+
* @param {Vector2} force */
|
|
1202
|
+
applyForce(force) { this.applyAcceleration(force.scale(1/this.mass)); }
|
|
1203
|
+
|
|
1204
|
+
/** Get the direction of the mirror
|
|
1205
|
+
* @return {Number} -1 if this.mirror is true, or 1 if not mirrored */
|
|
1206
|
+
getMirrorSign() { return this.mirror ? -1 : 1; }
|
|
1141
1207
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1208
|
+
/** Attaches a child to this with a given local transform
|
|
1209
|
+
* @param {EngineObject} child
|
|
1210
|
+
* @param {Vector2} [localPos=Vector2()]
|
|
1211
|
+
* @param {Number} [localAngle=0] */
|
|
1212
|
+
addChild(child, localPos=vec2(), localAngle=0)
|
|
1213
|
+
{
|
|
1214
|
+
ASSERT(!child.parent && !this.children.includes(child));
|
|
1215
|
+
this.children.push(child);
|
|
1216
|
+
child.parent = this;
|
|
1217
|
+
child.localPos = localPos.copy();
|
|
1218
|
+
child.localAngle = localAngle;
|
|
1151
1219
|
}
|
|
1152
|
-
for (const o of engineObjects)
|
|
1153
|
-
o.parent || updateObject(o);
|
|
1154
|
-
|
|
1155
|
-
// remove destroyed objects
|
|
1156
|
-
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
1157
|
-
|
|
1158
|
-
// increment frame and update time
|
|
1159
|
-
time = ++frame / frameRate;
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
/** Destroy and remove all objects
|
|
1163
|
-
* @memberof Engine */
|
|
1164
|
-
function engineObjectsDestroy()
|
|
1165
|
-
{
|
|
1166
|
-
for (const o of engineObjects)
|
|
1167
|
-
o.parent || o.destroy();
|
|
1168
|
-
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
1169
|
-
}
|
|
1170
1220
|
|
|
1171
|
-
/**
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
* @param {Function} [callbackFunction] - Calls this function on every object that passes the test
|
|
1175
|
-
* @param {Array} [objects=engineObjects] - List of objects to check
|
|
1176
|
-
* @memberof Engine */
|
|
1177
|
-
function engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
|
|
1178
|
-
{
|
|
1179
|
-
if (!pos) // all objects
|
|
1221
|
+
/** Removes a child from this one
|
|
1222
|
+
* @param {EngineObject} child */
|
|
1223
|
+
removeChild(child)
|
|
1180
1224
|
{
|
|
1181
|
-
|
|
1182
|
-
|
|
1225
|
+
ASSERT(child.parent == this && this.children.includes(child));
|
|
1226
|
+
this.children.splice(this.children.indexOf(child), 1);
|
|
1227
|
+
child.parent = 0;
|
|
1183
1228
|
}
|
|
1184
|
-
|
|
1229
|
+
|
|
1230
|
+
/** Set how this object collides
|
|
1231
|
+
* @param {Boolean} [collideSolidObjects=1] - Does it collide with solid objects
|
|
1232
|
+
* @param {Boolean} [isSolid=1] - Does it collide with and block other objects (expensive in large numbers)
|
|
1233
|
+
* @param {Boolean} [collideTiles=1] - Does it collide with the tile collision */
|
|
1234
|
+
setCollision(collideSolidObjects=1, isSolid=1, collideTiles=1)
|
|
1185
1235
|
{
|
|
1186
|
-
|
|
1187
|
-
|
|
1236
|
+
ASSERT(collideSolidObjects || !isSolid); // solid objects must be set to collide
|
|
1237
|
+
|
|
1238
|
+
this.collideSolidObjects = collideSolidObjects;
|
|
1239
|
+
this.isSolid = isSolid;
|
|
1240
|
+
this.collideTiles = collideTiles;
|
|
1188
1241
|
}
|
|
1189
|
-
|
|
1242
|
+
|
|
1243
|
+
/** Returns string containg info about this object for debugging
|
|
1244
|
+
* @return {String} */
|
|
1245
|
+
toString()
|
|
1190
1246
|
{
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1247
|
+
if (debug)
|
|
1248
|
+
{
|
|
1249
|
+
let text = 'type = ' + this.constructor.name;
|
|
1250
|
+
if (this.pos.x || this.pos.y)
|
|
1251
|
+
text += '\npos = ' + this.pos;
|
|
1252
|
+
if (this.velocity.x || this.velocity.y)
|
|
1253
|
+
text += '\nvelocity = ' + this.velocity;
|
|
1254
|
+
if (this.size.x || this.size.y)
|
|
1255
|
+
text += '\nsize = ' + this.size;
|
|
1256
|
+
if (this.angle)
|
|
1257
|
+
text += '\nangle = ' + this.angle.toFixed(3);
|
|
1258
|
+
if (this.color)
|
|
1259
|
+
text += '\ncolor = ' + this.color;
|
|
1260
|
+
return text;
|
|
1261
|
+
}
|
|
1194
1262
|
}
|
|
1195
|
-
}
|
|
1263
|
+
}
|
|
1196
1264
|
/**
|
|
1197
|
-
* LittleJS
|
|
1265
|
+
* LittleJS Drawing System
|
|
1266
|
+
* - Hybrid with both Canvas2D and WebGL available
|
|
1267
|
+
* - Super fast tile sheet rendering with WebGL
|
|
1268
|
+
* - Can apply rotation, mirror, color and additive color
|
|
1269
|
+
* - Many useful utility functions
|
|
1270
|
+
*
|
|
1271
|
+
* LittleJS uses a hybrid rendering solution with the best of both Canvas2D and WebGL.
|
|
1272
|
+
* There are 3 canvas/contexts available to draw to...
|
|
1273
|
+
* mainCanvas - 2D background canvas, non WebGL stuff like tile layers are drawn here.
|
|
1274
|
+
* glCanvas - Used by the accelerated WebGL batch rendering system.
|
|
1275
|
+
* overlayCanvas - Another 2D canvas that appears on top of the other 2 canvases.
|
|
1276
|
+
*
|
|
1277
|
+
* The WebGL rendering system is very fast with some caveats...
|
|
1278
|
+
* - The default setup supports only 1 tile sheet, to support more call glCreateTexture and glSetTexture
|
|
1279
|
+
* - Switching blend modes (additive) or textures causes another draw call which is expensive in excess
|
|
1280
|
+
* - Group additive rendering together using renderOrder to mitigate this issue
|
|
1281
|
+
*
|
|
1282
|
+
* The LittleJS rendering solution is intentionally simple, feel free to adjust it for your needs!
|
|
1283
|
+
* @namespace Draw
|
|
1198
1284
|
*/
|
|
1199
1285
|
|
|
1200
1286
|
'use strict';
|
|
1201
1287
|
|
|
1202
|
-
/**
|
|
1203
|
-
*
|
|
1204
|
-
*
|
|
1205
|
-
|
|
1206
|
-
* - Will be updated and rendered each frame
|
|
1207
|
-
* - Renders as a sprite from a tilesheet by default
|
|
1208
|
-
* - Can have color and addtive color applied
|
|
1209
|
-
* - 2d Physics and collision system
|
|
1210
|
-
* - Sorted by renderOrder
|
|
1211
|
-
* - Objects can have children attached
|
|
1212
|
-
* - Parents are updated before children, and set child transform
|
|
1213
|
-
* - Call destroy() to get rid of objects
|
|
1214
|
-
*
|
|
1215
|
-
* The physics system used by objects is simple and fast with some caveats...
|
|
1216
|
-
* - Collision uses the axis aligned size, the object's rotation angle is only for rendering
|
|
1217
|
-
* - Objects are guaranteed to not intersect tile collision from physics
|
|
1218
|
-
* - If an object starts or is moved inside tile collision, it will not collide with that tile
|
|
1219
|
-
* - Collision for objects can be set to be solid to block other objects
|
|
1220
|
-
* - Objects may get pushed into overlapping other solid objects, if so they will push away
|
|
1221
|
-
* - Solid objects are more performance intensive and should be used sparingly
|
|
1222
|
-
* @example
|
|
1223
|
-
* // create an engine object, normally you would first extend the class with your own
|
|
1224
|
-
* const pos = vec2(2,3);
|
|
1225
|
-
* const object = new EngineObject(pos);
|
|
1226
|
-
*/
|
|
1227
|
-
class EngineObject
|
|
1228
|
-
{
|
|
1229
|
-
/** Create an engine object and adds it to the list of objects
|
|
1230
|
-
* @param {Vector2} [position=Vector2()] - World space position of the object
|
|
1231
|
-
* @param {Vector2} [size=Vector2(1,1)] - World space size of the object
|
|
1232
|
-
* @param {Number} [tileIndex=-1] - Tile to use to render object (-1 is untextured)
|
|
1233
|
-
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
|
|
1234
|
-
* @param {Number} [angle=0] - Angle the object is rotated by
|
|
1235
|
-
* @param {Color} [color=Color()] - Color to apply to tile when rendered
|
|
1236
|
-
* @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
|
|
1237
|
-
*/
|
|
1238
|
-
constructor(pos=vec2(), size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, angle=0, color, renderOrder=0)
|
|
1239
|
-
{
|
|
1240
|
-
// set passed in params
|
|
1241
|
-
ASSERT(isVector2(pos) && isVector2(size)); // ensure pos and size are vec2s
|
|
1242
|
-
|
|
1243
|
-
/** @property {Vector2} - World space position of the object */
|
|
1244
|
-
this.pos = pos.copy();
|
|
1245
|
-
/** @property {Vector2} - World space width and height of the object */
|
|
1246
|
-
this.size = size;
|
|
1247
|
-
/** @property {Vector2} - Size of object used for drawing, uses size if not set */
|
|
1248
|
-
this.drawSize;
|
|
1249
|
-
/** @property {Number} - Tile to use to render object (-1 is untextured) */
|
|
1250
|
-
this.tileIndex = tileIndex;
|
|
1251
|
-
/** @property {Vector2} - Size of tile in source pixels */
|
|
1252
|
-
this.tileSize = tileSize;
|
|
1253
|
-
/** @property {Number} - Angle to rotate the object */
|
|
1254
|
-
this.angle = angle;
|
|
1255
|
-
/** @property {Color} - Color to apply when rendered */
|
|
1256
|
-
this.color = color;
|
|
1257
|
-
/** @property {Color} - Additive color to apply when rendered */
|
|
1258
|
-
this.additiveColor;
|
|
1288
|
+
/** The primary 2D canvas visible to the user
|
|
1289
|
+
* @type {HTMLCanvasElement}
|
|
1290
|
+
* @memberof Draw */
|
|
1291
|
+
let mainCanvas;
|
|
1259
1292
|
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
this.damping = objectDefaultDamping;
|
|
1265
|
-
/** @property {Number} [angleDamping=objectDefaultAngleDamping] - How much to slow down rotation each frame (0-1) */
|
|
1266
|
-
this.angleDamping = objectDefaultAngleDamping;
|
|
1267
|
-
/** @property {Number} [elasticity=objectDefaultElasticity] - How bouncy the object is when colliding (0-1) */
|
|
1268
|
-
this.elasticity = objectDefaultElasticity;
|
|
1269
|
-
/** @property {Number} [friction=objectDefaultFriction] - How much friction to apply when sliding (0-1) */
|
|
1270
|
-
this.friction = objectDefaultFriction;
|
|
1271
|
-
/** @property {Number} [gravityScale=1] - How much to scale gravity by for this object */
|
|
1272
|
-
this.gravityScale = 1;
|
|
1273
|
-
/** @property {Number} [renderOrder=0] - Objects are sorted by render order */
|
|
1274
|
-
this.renderOrder = renderOrder;
|
|
1275
|
-
/** @property {Vector2} [velocity=Vector2()] - Velocity of the object */
|
|
1276
|
-
this.velocity = new Vector2();
|
|
1277
|
-
/** @property {Number} [angleVelocity=0] - Angular velocity of the object */
|
|
1278
|
-
this.angleVelocity = 0;
|
|
1293
|
+
/** 2d context for mainCanvas
|
|
1294
|
+
* @type {CanvasRenderingContext2D}
|
|
1295
|
+
* @memberof Draw */
|
|
1296
|
+
let mainContext;
|
|
1279
1297
|
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1298
|
+
/** A canvas that appears on top of everything the same size as mainCanvas
|
|
1299
|
+
* @type {HTMLCanvasElement}
|
|
1300
|
+
* @memberof Draw */
|
|
1301
|
+
let overlayCanvas;
|
|
1284
1302
|
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
/** Update the object transform and physics, called automatically by engine once each frame */
|
|
1290
|
-
update()
|
|
1291
|
-
{
|
|
1292
|
-
const parent = this.parent;
|
|
1293
|
-
if (parent)
|
|
1294
|
-
{
|
|
1295
|
-
// copy parent pos/angle
|
|
1296
|
-
this.pos = this.localPos.multiply(vec2(parent.getMirrorSign(),1)).rotate(-parent.angle).add(parent.pos);
|
|
1297
|
-
this.angle = parent.getMirrorSign()*this.localAngle + parent.angle;
|
|
1298
|
-
return;
|
|
1299
|
-
}
|
|
1303
|
+
/** 2d context for overlayCanvas
|
|
1304
|
+
* @type {CanvasRenderingContext2D}
|
|
1305
|
+
* @memberof Draw */
|
|
1306
|
+
let overlayContext;
|
|
1300
1307
|
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1308
|
+
/** The size of the main canvas (and other secondary canvases)
|
|
1309
|
+
* @type {Vector2}
|
|
1310
|
+
* @memberof Draw */
|
|
1311
|
+
let mainCanvasSize = vec2();
|
|
1304
1312
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
this.pos.y += this.velocity.y *= this.damping;
|
|
1310
|
-
this.angle += this.angleVelocity *= this.angleDamping;
|
|
1313
|
+
/** Tile sheet for batch rendering system
|
|
1314
|
+
* @type {CanvasImageSource}
|
|
1315
|
+
* @memberof Draw */
|
|
1316
|
+
const tileImage = new Image;
|
|
1311
1317
|
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
ASSERT(this.damping >= 0 && this.damping <= 1);
|
|
1318
|
+
// Engine internal variables not exposed to documentation
|
|
1319
|
+
let tileImageSize, tileImageFixBleed, drawCount;
|
|
1315
1320
|
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
{
|
|
1331
|
-
// check collisions against solid objects
|
|
1332
|
-
const epsilon = .001; // necessary to push slightly outside of the collision
|
|
1333
|
-
for (const o of engineObjectsCollide)
|
|
1334
|
-
{
|
|
1335
|
-
// non solid objects don't collide with eachother
|
|
1336
|
-
if (!this.isSolid & !o.isSolid || o.destroyed || o.parent || o == this)
|
|
1337
|
-
continue;
|
|
1338
|
-
|
|
1339
|
-
// check collision
|
|
1340
|
-
if (!isOverlapping(this.pos, this.size, o.pos, o.size))
|
|
1341
|
-
continue;
|
|
1342
|
-
|
|
1343
|
-
// pass collision to objects
|
|
1344
|
-
if (!this.collideWithObject(o) | !o.collideWithObject(this))
|
|
1345
|
-
continue;
|
|
1346
|
-
|
|
1347
|
-
if (isOverlapping(oldPos, this.size, o.pos, o.size))
|
|
1348
|
-
{
|
|
1349
|
-
// if already was touching, try to push away
|
|
1350
|
-
const deltaPos = oldPos.subtract(o.pos);
|
|
1351
|
-
const length = deltaPos.length();
|
|
1352
|
-
const pushAwayAccel = .001; // push away if already overlapping
|
|
1353
|
-
const velocity = length < .01 ? randVector(pushAwayAccel) : deltaPos.scale(pushAwayAccel/length);
|
|
1354
|
-
this.velocity = this.velocity.add(velocity);
|
|
1355
|
-
if (o.mass) // push away if not fixed
|
|
1356
|
-
o.velocity = o.velocity.subtract(velocity);
|
|
1357
|
-
|
|
1358
|
-
debugOverlay && debugPhysics && debugAABB(this.pos, this.size, o.pos, o.size, '#f00');
|
|
1359
|
-
continue;
|
|
1360
|
-
}
|
|
1361
|
-
|
|
1362
|
-
// check for collision
|
|
1363
|
-
const sizeBoth = this.size.add(o.size);
|
|
1364
|
-
const smallStepUp = (oldPos.y - o.pos.y)*2 > sizeBoth.y + gravity; // prefer to push up if small delta
|
|
1365
|
-
const isBlockedX = abs(oldPos.y - o.pos.y)*2 < sizeBoth.y;
|
|
1366
|
-
const isBlockedY = abs(oldPos.x - o.pos.x)*2 < sizeBoth.x;
|
|
1367
|
-
const elasticity = max(this.elasticity, o.elasticity);
|
|
1368
|
-
|
|
1369
|
-
if (smallStepUp | isBlockedY | !isBlockedX) // resolve y collision
|
|
1370
|
-
{
|
|
1371
|
-
// push outside object collision
|
|
1372
|
-
this.pos.y = o.pos.y + (sizeBoth.y/2 + epsilon) * sign(oldPos.y - o.pos.y);
|
|
1373
|
-
if (o.groundObject && wasMovingDown || !o.mass)
|
|
1374
|
-
{
|
|
1375
|
-
// set ground object if landed on something
|
|
1376
|
-
if (wasMovingDown)
|
|
1377
|
-
this.groundObject = o;
|
|
1378
|
-
|
|
1379
|
-
// bounce if other object is fixed or grounded
|
|
1380
|
-
this.velocity.y *= -elasticity;
|
|
1381
|
-
}
|
|
1382
|
-
else if (o.mass)
|
|
1383
|
-
{
|
|
1384
|
-
// inelastic collision
|
|
1385
|
-
const inelastic = (this.mass * this.velocity.y + o.mass * o.velocity.y) / (this.mass + o.mass);
|
|
1386
|
-
|
|
1387
|
-
// elastic collision
|
|
1388
|
-
const elastic0 = this.velocity.y * (this.mass - o.mass) / (this.mass + o.mass)
|
|
1389
|
-
+ o.velocity.y * 2 * o.mass / (this.mass + o.mass);
|
|
1390
|
-
const elastic1 = o.velocity.y * (o.mass - this.mass) / (this.mass + o.mass)
|
|
1391
|
-
+ this.velocity.y * 2 * this.mass / (this.mass + o.mass);
|
|
1392
|
-
|
|
1393
|
-
// lerp betwen elastic or inelastic based on elasticity
|
|
1394
|
-
this.velocity.y = lerp(elasticity, inelastic, elastic0);
|
|
1395
|
-
o.velocity.y = lerp(elasticity, inelastic, elastic1);
|
|
1396
|
-
}
|
|
1397
|
-
}
|
|
1398
|
-
if (!smallStepUp & isBlockedX) // resolve x collision
|
|
1399
|
-
{
|
|
1400
|
-
// push outside collision
|
|
1401
|
-
this.pos.x = o.pos.x + (sizeBoth.x/2 + epsilon) * sign(oldPos.x - o.pos.x);
|
|
1402
|
-
if (o.mass)
|
|
1403
|
-
{
|
|
1404
|
-
// inelastic collision
|
|
1405
|
-
const inelastic = (this.mass * this.velocity.x + o.mass * o.velocity.x) / (this.mass + o.mass);
|
|
1406
|
-
|
|
1407
|
-
// elastic collision
|
|
1408
|
-
const elastic0 = this.velocity.x * (this.mass - o.mass) / (this.mass + o.mass)
|
|
1409
|
-
+ o.velocity.x * 2 * o.mass / (this.mass + o.mass);
|
|
1410
|
-
const elastic1 = o.velocity.x * (o.mass - this.mass) / (this.mass + o.mass)
|
|
1411
|
-
+ this.velocity.x * 2 * this.mass / (this.mass + o.mass);
|
|
1412
|
-
|
|
1413
|
-
// lerp betwen elastic or inelastic based on elasticity
|
|
1414
|
-
this.velocity.x = lerp(elasticity, inelastic, elastic0);
|
|
1415
|
-
o.velocity.x = lerp(elasticity, inelastic, elastic1);
|
|
1416
|
-
}
|
|
1417
|
-
else // bounce if other object is fixed
|
|
1418
|
-
this.velocity.x *= -elasticity;
|
|
1419
|
-
}
|
|
1420
|
-
debugOverlay && debugPhysics && debugAABB(this.pos, this.size, o.pos, o.size, '#f0f');
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
if (this.collideTiles)
|
|
1424
|
-
{
|
|
1425
|
-
// check collision against tiles
|
|
1426
|
-
if (tileCollisionTest(this.pos, this.size, this))
|
|
1427
|
-
{
|
|
1428
|
-
// if already was stuck in collision, don't do anything
|
|
1429
|
-
// this should not happen unless something starts in collision
|
|
1430
|
-
if (!tileCollisionTest(oldPos, this.size, this))
|
|
1431
|
-
{
|
|
1432
|
-
// test which side we bounced off (or both if a corner)
|
|
1433
|
-
const isBlockedY = tileCollisionTest(new Vector2(oldPos.x, this.pos.y), this.size, this);
|
|
1434
|
-
const isBlockedX = tileCollisionTest(new Vector2(this.pos.x, oldPos.y), this.size, this);
|
|
1435
|
-
if (isBlockedY | !isBlockedX)
|
|
1436
|
-
{
|
|
1437
|
-
// set if landed on ground
|
|
1438
|
-
this.groundObject = wasMovingDown;
|
|
1439
|
-
|
|
1440
|
-
// bounce velocity
|
|
1441
|
-
this.velocity.y *= -this.elasticity;
|
|
1442
|
-
|
|
1443
|
-
// adjust next velocity to settle on ground
|
|
1444
|
-
const o = (oldPos.y - this.size.y/2|0) - (oldPos.y - this.size.y/2);
|
|
1445
|
-
if (o < 0 && o > this.damping * this.velocity.y + gravity * this.gravityScale)
|
|
1446
|
-
this.velocity.y = this.damping ? (o - gravity * this.gravityScale) / this.damping : 0;
|
|
1447
|
-
|
|
1448
|
-
// move to previous position
|
|
1449
|
-
this.pos.y = oldPos.y;
|
|
1450
|
-
}
|
|
1451
|
-
if (isBlockedX)
|
|
1452
|
-
{
|
|
1453
|
-
// move to previous position and bounce
|
|
1454
|
-
this.pos.x = oldPos.x;
|
|
1455
|
-
this.velocity.x *= -this.elasticity;
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
}
|
|
1459
|
-
}
|
|
1460
|
-
}
|
|
1461
|
-
|
|
1462
|
-
/** Render the object, draws a tile by default, automatically called each frame, sorted by renderOrder */
|
|
1463
|
-
render()
|
|
1464
|
-
{
|
|
1465
|
-
// default object render
|
|
1466
|
-
drawTile(this.pos, this.drawSize || this.size, this.tileIndex, this.tileSize, this.color, this.angle, this.mirror, this.additiveColor);
|
|
1467
|
-
}
|
|
1468
|
-
|
|
1469
|
-
/** Destroy this object, destroy it's children, detach it's parent, and mark it for removal */
|
|
1470
|
-
destroy()
|
|
1471
|
-
{
|
|
1472
|
-
if (this.destroyed)
|
|
1473
|
-
return;
|
|
1474
|
-
|
|
1475
|
-
// disconnect from parent and destroy chidren
|
|
1476
|
-
this.destroyed = 1;
|
|
1477
|
-
this.parent && this.parent.removeChild(this);
|
|
1478
|
-
for (const child of this.children)
|
|
1479
|
-
child.destroy(child.parent = 0);
|
|
1480
|
-
}
|
|
1481
|
-
|
|
1482
|
-
/** Called to check if a tile collision should be resolved
|
|
1483
|
-
* @param {Number} tileData - the value of the tile at the position
|
|
1484
|
-
* @param {Vector2} pos - tile where the collision occured
|
|
1485
|
-
* @return {Boolean} - true if the collision should be resolved */
|
|
1486
|
-
collideWithTile(tileData, pos) { return tileData > 0; }
|
|
1487
|
-
|
|
1488
|
-
/** Called to check if a tile raycast hit
|
|
1489
|
-
* @param {Number} tileData - the value of the tile at the position
|
|
1490
|
-
* @param {Vector2} pos - tile where the raycast is
|
|
1491
|
-
* @return {Boolean} - true if the raycast should hit */
|
|
1492
|
-
collideWithTileRaycast(tileData, pos) { return tileData > 0; }
|
|
1493
|
-
|
|
1494
|
-
/** Called to check if a object collision should be resolved
|
|
1495
|
-
* @param {EngineObject} object - the object to test against
|
|
1496
|
-
* @return {Boolean} - true if the collision should be resolved
|
|
1497
|
-
*/
|
|
1498
|
-
collideWithObject(o) { return 1; }
|
|
1499
|
-
|
|
1500
|
-
/** How long since the object was created
|
|
1501
|
-
* @return {Number} */
|
|
1502
|
-
getAliveTime() { return time - this.spawnTime; }
|
|
1503
|
-
|
|
1504
|
-
/** Apply acceleration to this object (adjust velocity, not affected by mass)
|
|
1505
|
-
* @param {Vector2} acceleration */
|
|
1506
|
-
applyAcceleration(a) { if (this.mass) this.velocity = this.velocity.add(a); }
|
|
1507
|
-
|
|
1508
|
-
/** Apply force to this object (adjust velocity, affected by mass)
|
|
1509
|
-
* @param {Vector2} force */
|
|
1510
|
-
applyForce(force) { this.applyAcceleration(force.scale(1/this.mass)); }
|
|
1511
|
-
|
|
1512
|
-
/** Get the direction of the mirror
|
|
1513
|
-
* @return {Number} -1 if this.mirror is true, or 1 if not mirrored */
|
|
1514
|
-
getMirrorSign() { return this.mirror ? -1 : 1; }
|
|
1515
|
-
|
|
1516
|
-
/** Attaches a child to this with a given local transform
|
|
1517
|
-
* @param {EngineObject} child
|
|
1518
|
-
* @param {Vector2} [localPos=Vector2()]
|
|
1519
|
-
* @param {Number} [localAngle=0] */
|
|
1520
|
-
addChild(child, localPos=vec2(), localAngle=0)
|
|
1521
|
-
{
|
|
1522
|
-
ASSERT(!child.parent && !this.children.includes(child));
|
|
1523
|
-
this.children.push(child);
|
|
1524
|
-
child.parent = this;
|
|
1525
|
-
child.localPos = localPos.copy();
|
|
1526
|
-
child.localAngle = localAngle;
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
|
-
/** Removes a child from this one
|
|
1530
|
-
* @param {EngineObject} child */
|
|
1531
|
-
removeChild(child)
|
|
1532
|
-
{
|
|
1533
|
-
ASSERT(child.parent == this && this.children.includes(child));
|
|
1534
|
-
this.children.splice(this.children.indexOf(child), 1);
|
|
1535
|
-
child.parent = 0;
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
|
-
/** Set how this object collides
|
|
1539
|
-
* @param {Boolean} [collideSolidObjects=1] - Does it collide with solid objects
|
|
1540
|
-
* @param {Boolean} [isSolid=1] - Does it collide with and block other objects (expensive in large numbers)
|
|
1541
|
-
* @param {Boolean} [collideTiles=1] - Does it collide with the tile collision */
|
|
1542
|
-
setCollision(collideSolidObjects=1, isSolid=1, collideTiles=1)
|
|
1543
|
-
{
|
|
1544
|
-
ASSERT(collideSolidObjects || !isSolid); // solid objects must be set to collide
|
|
1545
|
-
|
|
1546
|
-
this.collideSolidObjects = collideSolidObjects;
|
|
1547
|
-
this.isSolid = isSolid;
|
|
1548
|
-
this.collideTiles = collideTiles;
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
|
-
/** Returns string containg info about this object for debugging
|
|
1552
|
-
* @return {String} */
|
|
1553
|
-
toString()
|
|
1554
|
-
{
|
|
1555
|
-
if (debug)
|
|
1556
|
-
{
|
|
1557
|
-
let text = 'type = ' + this.constructor.name;
|
|
1558
|
-
if (this.pos.x || this.pos.y)
|
|
1559
|
-
text += '\npos = ' + this.pos;
|
|
1560
|
-
if (this.velocity.x || this.velocity.y)
|
|
1561
|
-
text += '\nvelocity = ' + this.velocity;
|
|
1562
|
-
if (this.size.x || this.size.y)
|
|
1563
|
-
text += '\nsize = ' + this.size;
|
|
1564
|
-
if (this.angle)
|
|
1565
|
-
text += '\nangle = ' + this.angle.toFixed(3);
|
|
1566
|
-
if (this.color)
|
|
1567
|
-
text += '\ncolor = ' + this.color;
|
|
1568
|
-
return text;
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
1571
|
-
}
|
|
1572
|
-
/**
|
|
1573
|
-
* LittleJS Drawing System
|
|
1574
|
-
* - Hybrid with both Canvas2D and WebGL available
|
|
1575
|
-
* - Super fast tile sheet rendering with WebGL
|
|
1576
|
-
* - Can apply rotation, mirror, color and additive color
|
|
1577
|
-
* - Many useful utility functions
|
|
1578
|
-
*
|
|
1579
|
-
* LittleJS uses a hybrid rendering solution with the best of both Canvas2D and WebGL.
|
|
1580
|
-
* There are 3 canvas/contexts available to draw to...
|
|
1581
|
-
* mainCanvas - 2D background canvas, non WebGL stuff like tile layers are drawn here.
|
|
1582
|
-
* glCanvas - Used by the accelerated WebGL batch rendering system.
|
|
1583
|
-
* overlayCanvas - Another 2D canvas that appears on top of the other 2 canvases.
|
|
1584
|
-
*
|
|
1585
|
-
* The WebGL rendering system is very fast with some caveats...
|
|
1586
|
-
* - The default setup supports only 1 tile sheet, to support more call glCreateTexture and glSetTexture
|
|
1587
|
-
* - Switching blend modes (additive) or textures causes another draw call which is expensive in excess
|
|
1588
|
-
* - Group additive rendering together using renderOrder to mitigate this issue
|
|
1589
|
-
*
|
|
1590
|
-
* The LittleJS rendering solution is intentionally simple, feel free to adjust it for your needs!
|
|
1591
|
-
* @namespace Draw
|
|
1592
|
-
*/
|
|
1593
|
-
|
|
1594
|
-
'use strict';
|
|
1595
|
-
|
|
1596
|
-
/** The primary 2D canvas visible to the user
|
|
1597
|
-
* @type {HTMLCanvasElement}
|
|
1598
|
-
* @memberof Draw */
|
|
1599
|
-
let mainCanvas;
|
|
1600
|
-
|
|
1601
|
-
/** 2d context for mainCanvas
|
|
1602
|
-
* @type {CanvasRenderingContext2D}
|
|
1603
|
-
* @memberof Draw */
|
|
1604
|
-
let mainContext;
|
|
1605
|
-
|
|
1606
|
-
/** A canvas that appears on top of everything the same size as mainCanvas
|
|
1607
|
-
* @type {HTMLCanvasElement}
|
|
1608
|
-
* @memberof Draw */
|
|
1609
|
-
let overlayCanvas;
|
|
1610
|
-
|
|
1611
|
-
/** 2d context for overlayCanvas
|
|
1612
|
-
* @type {CanvasRenderingContext2D}
|
|
1613
|
-
* @memberof Draw */
|
|
1614
|
-
let overlayContext;
|
|
1615
|
-
|
|
1616
|
-
/** The size of the main canvas (and other secondary canvases)
|
|
1617
|
-
* @type {Vector2}
|
|
1618
|
-
* @memberof Draw */
|
|
1619
|
-
let mainCanvasSize = vec2();
|
|
1620
|
-
|
|
1621
|
-
/** Tile sheet for batch rendering system
|
|
1622
|
-
* @type {CanvasImageSource}
|
|
1623
|
-
* @memberof Draw */
|
|
1624
|
-
const tileImage = new Image;
|
|
1625
|
-
|
|
1626
|
-
// Engine internal variables not exposed to documentation
|
|
1627
|
-
let tileImageSize, tileImageFixBleed, drawCount;
|
|
1628
|
-
|
|
1629
|
-
/** Convert from screen to world space coordinates
|
|
1630
|
-
* - if calling outside of render, you may need to manually set mainCanvasSize
|
|
1631
|
-
* @param {Vector2} screenPos
|
|
1632
|
-
* @return {Vector2}
|
|
1633
|
-
* @memberof Draw */
|
|
1634
|
-
function screenToWorld(screenPos)
|
|
1635
|
-
{
|
|
1636
|
-
ASSERT(mainCanvasSize.x && mainCanvasSize.y, 'mainCanvasSize is invalid');
|
|
1637
|
-
return screenPos.add(vec2(.5)).subtract(mainCanvasSize.scale(.5)).multiply(vec2(1/cameraScale,-1/cameraScale)).add(cameraPos);
|
|
1638
|
-
}
|
|
1321
|
+
/** Convert from screen to world space coordinates
|
|
1322
|
+
* - if calling outside of render, you may need to manually set mainCanvasSize
|
|
1323
|
+
* @param {Vector2} screenPos
|
|
1324
|
+
* @return {Vector2}
|
|
1325
|
+
* @memberof Draw */
|
|
1326
|
+
function screenToWorld(screenPos)
|
|
1327
|
+
{
|
|
1328
|
+
ASSERT(mainCanvasSize.x && mainCanvasSize.y, 'mainCanvasSize is invalid');
|
|
1329
|
+
return screenPos
|
|
1330
|
+
.add(vec2(.5))
|
|
1331
|
+
.subtract(mainCanvasSize.scale(.5))
|
|
1332
|
+
.multiply(vec2(1/cameraScale,-1/cameraScale))
|
|
1333
|
+
.add(cameraPos);
|
|
1334
|
+
}
|
|
1639
1335
|
|
|
1640
1336
|
/** Convert from world to screen space coordinates
|
|
1641
1337
|
* - if calling outside of render, you may need to manually set mainCanvasSize
|
|
@@ -1645,7 +1341,11 @@ function screenToWorld(screenPos)
|
|
|
1645
1341
|
function worldToScreen(worldPos)
|
|
1646
1342
|
{
|
|
1647
1343
|
ASSERT(mainCanvasSize.x && mainCanvasSize.y, 'mainCanvasSize is invalid');
|
|
1648
|
-
return worldPos
|
|
1344
|
+
return worldPos
|
|
1345
|
+
.subtract(cameraPos)
|
|
1346
|
+
.multiply(vec2(cameraScale,-cameraScale))
|
|
1347
|
+
.add(mainCanvasSize.scale(.5))
|
|
1348
|
+
.subtract(vec2(.5));
|
|
1649
1349
|
}
|
|
1650
1350
|
|
|
1651
1351
|
/** Draw textured tile centered in world space, with color applied if using WebGL
|
|
@@ -1659,8 +1359,8 @@ function worldToScreen(worldPos)
|
|
|
1659
1359
|
* @param {Color} [additiveColor=Color(0,0,0,0)] - Additive color to be applied
|
|
1660
1360
|
* @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
|
|
1661
1361
|
* @memberof Draw */
|
|
1662
|
-
function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color,
|
|
1663
|
-
additiveColor=new Color(0,0,0,0), useWebGL=glEnable)
|
|
1362
|
+
function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color,
|
|
1363
|
+
angle=0, mirror, additiveColor=new Color(0,0,0,0), useWebGL=glEnable)
|
|
1664
1364
|
{
|
|
1665
1365
|
showWatermark && ++drawCount;
|
|
1666
1366
|
if (glEnable && useWebGL)
|
|
@@ -1953,7 +1653,7 @@ function toggleFullscreen()
|
|
|
1953
1653
|
else if (document.body.requestFullscreen)
|
|
1954
1654
|
document.body.requestFullscreen();
|
|
1955
1655
|
}
|
|
1956
|
-
|
|
1656
|
+
|
|
1957
1657
|
/**
|
|
1958
1658
|
* LittleJS Input System
|
|
1959
1659
|
* - Tracks key down, pressed, and released
|
|
@@ -2387,7 +2087,7 @@ function touchGamepadRender()
|
|
|
2387
2087
|
const rightCenter = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
2388
2088
|
for (let i=4; i--;)
|
|
2389
2089
|
{
|
|
2390
|
-
const pos = rightCenter.add((
|
|
2090
|
+
const pos = rightCenter.add(vec2().setAngle(i*PI/2, touchGamepadSize/2));
|
|
2391
2091
|
overlayContext.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
|
|
2392
2092
|
overlayContext.beginPath();
|
|
2393
2093
|
overlayContext.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
|
|
@@ -2397,7 +2097,7 @@ function touchGamepadRender()
|
|
|
2397
2097
|
|
|
2398
2098
|
// set canvas back to normal
|
|
2399
2099
|
overlayContext.restore();
|
|
2400
|
-
}
|
|
2100
|
+
}
|
|
2401
2101
|
/**
|
|
2402
2102
|
* LittleJS Audio System
|
|
2403
2103
|
* - <a href=https://killedbyapixel.github.io/ZzFX/>ZzFX Sound Effects</a> - Sound Effect Generator
|
|
@@ -2838,7 +2538,7 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
2838
2538
|
patternChannel = patterns[patternIndex][channelIndex] || [0, 0, 0];
|
|
2839
2539
|
|
|
2840
2540
|
// check if there are more channels
|
|
2841
|
-
hasMore
|
|
2541
|
+
hasMore ||= !!patterns[patternIndex][channelIndex];
|
|
2842
2542
|
|
|
2843
2543
|
// get next offset, use the length of first channel
|
|
2844
2544
|
nextSampleOffset = outSampleOffset + (patterns[patternIndex][0].length - 2 - !notFirstBeat) * beatLength;
|
|
@@ -2851,7 +2551,7 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
2851
2551
|
|
|
2852
2552
|
// stop if end, different instrument or new note
|
|
2853
2553
|
stop = i == patternChannel.length + isSequenceEnd - 1 && isSequenceEnd ||
|
|
2854
|
-
instrument != (patternChannel[0] || 0)
|
|
2554
|
+
instrument != (patternChannel[0] || 0) || note;
|
|
2855
2555
|
|
|
2856
2556
|
// fill buffer with samples for previous beat, most cpu intensive part
|
|
2857
2557
|
for (j = 0; j < beatLength && notFirstBeat;
|
|
@@ -2895,7 +2595,7 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
2895
2595
|
}
|
|
2896
2596
|
|
|
2897
2597
|
return [leftChannelBuffer, rightChannelBuffer];
|
|
2898
|
-
}
|
|
2598
|
+
}
|
|
2899
2599
|
/**
|
|
2900
2600
|
* LittleJS Tile Layer System
|
|
2901
2601
|
* - Caches arrays of tiles to off screen canvas for fast rendering
|
|
@@ -2965,1303 +2665,1638 @@ function tileCollisionTest(pos, size=vec2(), object)
|
|
|
2965
2665
|
for (let x = minX; x < maxX; ++x)
|
|
2966
2666
|
{
|
|
2967
2667
|
const tileData = tileCollision[y*tileCollisionSize.x+x];
|
|
2968
|
-
if (tileData && (!object || object.collideWithTile(tileData,
|
|
2668
|
+
if (tileData && (!object || object.collideWithTile(tileData, vec2(x, y))))
|
|
2969
2669
|
return 1;
|
|
2970
2670
|
}
|
|
2971
|
-
}
|
|
2972
|
-
|
|
2973
|
-
/** Return the center of tile if any that is hit (
|
|
2974
|
-
* @param {Vector2} posStart
|
|
2975
|
-
* @param {Vector2} posEnd
|
|
2976
|
-
* @param {EngineObject} [object]
|
|
2977
|
-
* @return {Vector2}
|
|
2978
|
-
* @memberof TileCollision */
|
|
2979
|
-
function tileCollisionRaycast(posStart, posEnd, object)
|
|
2980
|
-
{
|
|
2981
|
-
// test if a ray collides with tiles from start to end
|
|
2982
|
-
// todo: a way to get the exact hit point, it must still
|
|
2983
|
-
const
|
|
2984
|
-
const
|
|
2985
|
-
const
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
/** Return the center of tile if any that is hit (does not return the exact intersection)
|
|
2674
|
+
* @param {Vector2} posStart
|
|
2675
|
+
* @param {Vector2} posEnd
|
|
2676
|
+
* @param {EngineObject} [object]
|
|
2677
|
+
* @return {Vector2}
|
|
2678
|
+
* @memberof TileCollision */
|
|
2679
|
+
function tileCollisionRaycast(posStart, posEnd, object)
|
|
2680
|
+
{
|
|
2681
|
+
// test if a ray collides with tiles from start to end
|
|
2682
|
+
// todo: a way to get the exact hit point, it must still be inside the hit tile
|
|
2683
|
+
const delta = posEnd.subtract(posStart);
|
|
2684
|
+
const totalLength = delta.length();
|
|
2685
|
+
const normalizedDelta = delta.normalize();
|
|
2686
|
+
const unit = vec2(abs(1/normalizedDelta.x), abs(1/normalizedDelta.y));
|
|
2687
|
+
const flooredPosStart = posStart.floor();
|
|
2688
|
+
|
|
2689
|
+
// setup iteration variables
|
|
2690
|
+
let pos = flooredPosStart;
|
|
2691
|
+
let xi = unit.x * (delta.x < 0 ? posStart.x - pos.x : pos.x - posStart.x + 1);
|
|
2692
|
+
let yi = unit.y * (delta.y < 0 ? posStart.y - pos.y : pos.y - posStart.y + 1);
|
|
2693
|
+
|
|
2694
|
+
while (1)
|
|
2695
|
+
{
|
|
2696
|
+
// check for tile collision
|
|
2697
|
+
const tileData = getTileCollisionData(pos);
|
|
2698
|
+
if (tileData && (!object || object.collideWithTile(tileData, pos)))
|
|
2699
|
+
{
|
|
2700
|
+
debugRaycast && debugLine(posStart, posEnd, '#f00', .02);
|
|
2701
|
+
debugRaycast && debugPoint(pos.add(vec2(.5)), '#ff0');
|
|
2702
|
+
return pos.add(vec2(.5));
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2705
|
+
// check if past the end
|
|
2706
|
+
if (xi > totalLength && yi > totalLength)
|
|
2707
|
+
break;
|
|
2708
|
+
|
|
2709
|
+
// get coordinates of the next tile to check
|
|
2710
|
+
if (xi > yi)
|
|
2711
|
+
pos.y += sign(delta.y), yi += unit.y;
|
|
2712
|
+
else
|
|
2713
|
+
pos.x += sign(delta.x), xi += unit.x;
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
debugRaycast && debugLine(posStart, posEnd, '#00f', .02);
|
|
2717
|
+
}
|
|
2718
|
+
|
|
2719
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2720
|
+
// Tile Layer Rendering System
|
|
2721
|
+
|
|
2722
|
+
/**
|
|
2723
|
+
* Tile layer data object stores info about how to render a tile
|
|
2724
|
+
* @example
|
|
2725
|
+
* // create tile layer data with tile index 0 and random orientation and color
|
|
2726
|
+
* const tileIndex = 0;
|
|
2727
|
+
* const direction = randInt(4)
|
|
2728
|
+
* const mirror = randInt(2);
|
|
2729
|
+
* const color = randColor();
|
|
2730
|
+
* const data = new TileLayerData(tileIndex, direction, mirror, color);
|
|
2731
|
+
*/
|
|
2732
|
+
class TileLayerData
|
|
2733
|
+
{
|
|
2734
|
+
/** Create a tile layer data object, one for each tile in a TileLayer
|
|
2735
|
+
* @param {Number} [tile] - The tile to use, untextured if undefined
|
|
2736
|
+
* @param {Number} [direction=0] - Integer direction of tile, in 90 degree increments
|
|
2737
|
+
* @param {Boolean} [mirror=0] - If the tile should be mirrored along the x axis
|
|
2738
|
+
* @param {Color} [color=Color()] - Color of the tile */
|
|
2739
|
+
constructor(tile, direction=0, mirror=0, color=new Color())
|
|
2740
|
+
{
|
|
2741
|
+
/** @property {Number} - The tile to use, untextured if undefined */
|
|
2742
|
+
this.tile = tile;
|
|
2743
|
+
/** @property {Number} - Integer direction of tile, in 90 degree increments */
|
|
2744
|
+
this.direction = direction;
|
|
2745
|
+
/** @property {Boolean} - If the tile should be mirrored along the x axis */
|
|
2746
|
+
this.mirror = mirror;
|
|
2747
|
+
/** @property {Color} - Color of the tile */
|
|
2748
|
+
this.color = color;
|
|
2749
|
+
}
|
|
2750
|
+
|
|
2751
|
+
/** Set this tile to clear, it will not be rendered */
|
|
2752
|
+
clear() { this.tile = this.direction = this.mirror = 0; color = new Color; }
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
/**
|
|
2756
|
+
* Tile layer object - cached rendering system for tile layers
|
|
2757
|
+
* - Each Tile layer is rendered to an off screen canvas
|
|
2758
|
+
* - To allow dynamic modifications, layers are rendered using canvas 2d
|
|
2759
|
+
* - Some devices like mobile phones are limited to 4k texture resolution
|
|
2760
|
+
* - So with 16x16 tiles this limits layers to 256x256 on mobile devices
|
|
2761
|
+
* @extends EngineObject
|
|
2762
|
+
* @example
|
|
2763
|
+
* // create tile collision and visible tile layer
|
|
2764
|
+
* initTileCollision(vec2(200,100));
|
|
2765
|
+
* const tileLayer = new TileLayer();
|
|
2766
|
+
*/
|
|
2767
|
+
class TileLayer extends EngineObject
|
|
2768
|
+
{
|
|
2769
|
+
/** Create a tile layer object
|
|
2770
|
+
* @param {Vector2} [position=Vector2()] - World space position
|
|
2771
|
+
* @param {Vector2} [size=tileCollisionSize] - World space size
|
|
2772
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tiles in source pixels
|
|
2773
|
+
* @param {Vector2} [scale=Vector2(1,1)] - How much to scale this layer when rendered
|
|
2774
|
+
* @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
|
|
2775
|
+
*/
|
|
2776
|
+
constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1), renderOrder=0)
|
|
2777
|
+
{
|
|
2778
|
+
super(pos, size, -1, tileSize, 0, undefined, renderOrder);
|
|
2779
|
+
|
|
2780
|
+
/** @property {HTMLCanvasElement} - The canvas used by this tile layer */
|
|
2781
|
+
this.canvas = document.createElement('canvas');
|
|
2782
|
+
/** @property {CanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
|
|
2783
|
+
this.context = this.canvas.getContext('2d');
|
|
2784
|
+
/** @property {Vector2} - How much to scale this layer when rendered */
|
|
2785
|
+
this.scale = scale;
|
|
2786
|
+
/** @property {Boolean} [isOverlay=0] - If true this layer will render to overlay canvas and appear above all objects */
|
|
2787
|
+
this.isOverlay;
|
|
2788
|
+
|
|
2789
|
+
// init tile data
|
|
2790
|
+
this.data = [];
|
|
2791
|
+
for (let j = this.size.area(); j--;)
|
|
2792
|
+
this.data.push(new TileLayerData);
|
|
2793
|
+
}
|
|
2794
|
+
|
|
2795
|
+
/** Set data at a given position in the array
|
|
2796
|
+
* @param {Vector2} position - Local position in array
|
|
2797
|
+
* @param {TileLayerData} data - Data to set
|
|
2798
|
+
* @param {Boolean} [redraw=0] - Force the tile to redraw if true */
|
|
2799
|
+
setData(layerPos, data, redraw)
|
|
2800
|
+
{
|
|
2801
|
+
if (layerPos.arrayCheck(this.size))
|
|
2802
|
+
{
|
|
2803
|
+
this.data[(layerPos.y|0)*this.size.x+layerPos.x|0] = data;
|
|
2804
|
+
redraw && this.drawTileData(layerPos);
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
|
|
2808
|
+
/** Get data at a given position in the array
|
|
2809
|
+
* @param {Vector2} layerPos - Local position in array
|
|
2810
|
+
* @return {TileLayerData} */
|
|
2811
|
+
getData(layerPos)
|
|
2812
|
+
{ return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0]; }
|
|
2813
|
+
|
|
2814
|
+
// Tile layers are not updated
|
|
2815
|
+
update() {}
|
|
2816
|
+
|
|
2817
|
+
// Render the tile layer, called automatically by the engine
|
|
2818
|
+
render()
|
|
2819
|
+
{
|
|
2820
|
+
ASSERT(mainContext != this.context); // must call redrawEnd() after drawing tiles
|
|
2821
|
+
|
|
2822
|
+
// flush and copy gl canvas because tile canvas does not use webgl
|
|
2823
|
+
glEnable && !glOverlay && !this.isOverlay && glCopyToContext(mainContext);
|
|
2824
|
+
|
|
2825
|
+
// draw the entire cached level onto the canvas
|
|
2826
|
+
const pos = worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));
|
|
2827
|
+
(this.isOverlay ? overlayContext : mainContext).drawImage
|
|
2828
|
+
(
|
|
2829
|
+
this.canvas, pos.x, pos.y,
|
|
2830
|
+
cameraScale*this.size.x*this.scale.x, cameraScale*this.size.y*this.scale.y
|
|
2831
|
+
);
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
/** Draw all the tile data to an offscreen canvas
|
|
2835
|
+
* - This may be slow in some browsers
|
|
2836
|
+
*/
|
|
2837
|
+
redraw()
|
|
2838
|
+
{
|
|
2839
|
+
this.redrawStart(1);
|
|
2840
|
+
this.drawAllTileData();
|
|
2841
|
+
this.redrawEnd();
|
|
2842
|
+
}
|
|
2843
|
+
|
|
2844
|
+
/** Call to start the redraw process
|
|
2845
|
+
* @param {Boolean} [clear=0] - Should it clear the canvas before drawing */
|
|
2846
|
+
redrawStart(clear = 0)
|
|
2847
|
+
{
|
|
2848
|
+
// save current render settings
|
|
2849
|
+
this.savedRenderSettings = [mainCanvas, mainContext, mainCanvasSize, cameraPos, cameraScale];
|
|
2850
|
+
|
|
2851
|
+
// hack: use normal rendering system to render the tiles
|
|
2852
|
+
mainCanvas = this.canvas;
|
|
2853
|
+
mainContext = this.context;
|
|
2854
|
+
cameraPos = this.size.scale(.5);
|
|
2855
|
+
cameraScale = this.tileSize.x;
|
|
2856
|
+
|
|
2857
|
+
if (clear)
|
|
2858
|
+
{
|
|
2859
|
+
// clear and set size
|
|
2860
|
+
mainCanvas.width = this.size.x * this.tileSize.x;
|
|
2861
|
+
mainCanvas.height = this.size.y * this.tileSize.y;
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2864
|
+
// begin a new render for the tile canvas
|
|
2865
|
+
enginePreRender();
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2868
|
+
/** Call to end the redraw process */
|
|
2869
|
+
redrawEnd()
|
|
2870
|
+
{
|
|
2871
|
+
ASSERT(mainContext == this.context); // must call redrawStart() before drawing tiles
|
|
2872
|
+
glEnable && glCopyToContext(mainContext, 1);
|
|
2873
|
+
//debugSaveCanvas(this.canvas);
|
|
2874
|
+
|
|
2875
|
+
// set stuff back to normal
|
|
2876
|
+
[mainCanvas, mainContext, mainCanvasSize, cameraPos, cameraScale] = this.savedRenderSettings;
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2879
|
+
/** Draw the tile at a given position
|
|
2880
|
+
* @param {Vector2} layerPos */
|
|
2881
|
+
drawTileData(layerPos)
|
|
2882
|
+
{
|
|
2883
|
+
// first clear out where the tile was
|
|
2884
|
+
const pos = layerPos.floor().add(this.pos).add(vec2(.5));
|
|
2885
|
+
this.drawCanvas2D(pos, vec2(1), 0, 0, (context)=>context.clearRect(-.5, -.5, 1, 1));
|
|
2886
|
+
|
|
2887
|
+
// draw the tile if not undefined
|
|
2888
|
+
const d = this.getData(layerPos);
|
|
2889
|
+
if (d.tile != undefined)
|
|
2890
|
+
{
|
|
2891
|
+
ASSERT(mainContext == this.context); // must call redrawStart() before drawing tiles
|
|
2892
|
+
drawTile(pos, vec2(1), d.tile, this.tileSize, d.color, d.direction*PI/2, d.mirror);
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
|
|
2896
|
+
/** Draw all the tiles in this layer */
|
|
2897
|
+
drawAllTileData()
|
|
2898
|
+
{
|
|
2899
|
+
for (let x = this.size.x; x--;)
|
|
2900
|
+
for (let y = this.size.y; y--;)
|
|
2901
|
+
this.drawTileData(vec2(x,y));
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2904
|
+
/** Draw directly to the 2D canvas in world space (bipass webgl)
|
|
2905
|
+
* @param {Vector2} pos
|
|
2906
|
+
* @param {Vector2} size
|
|
2907
|
+
* @param {Number} [angle=0]
|
|
2908
|
+
* @param {Boolean} [mirror=0]
|
|
2909
|
+
* @param {Function} drawFunction */
|
|
2910
|
+
drawCanvas2D(pos, size, angle=0, mirror, drawFunction)
|
|
2911
|
+
{
|
|
2912
|
+
const context = this.context;
|
|
2913
|
+
context.save();
|
|
2914
|
+
pos = pos.subtract(this.pos).multiply(this.tileSize);
|
|
2915
|
+
size = size.multiply(this.tileSize);
|
|
2916
|
+
context.translate(pos.x, this.canvas.height - pos.y);
|
|
2917
|
+
context.rotate(angle);
|
|
2918
|
+
context.scale(mirror ? -size.x : size.x, size.y);
|
|
2919
|
+
drawFunction(context);
|
|
2920
|
+
context.restore();
|
|
2921
|
+
}
|
|
2986
2922
|
|
|
2987
|
-
|
|
2923
|
+
/** Draw a tile directly onto the layer canvas
|
|
2924
|
+
* @param {Vector2} pos
|
|
2925
|
+
* @param {Vector2} [size=Vector2(1,1)]
|
|
2926
|
+
* @param {Number} [tileIndex=-1]
|
|
2927
|
+
* @param {Vector2} [tileSize=tileSizeDefault]
|
|
2928
|
+
* @param {Color} [color=Color()]
|
|
2929
|
+
* @param {Number} [angle=0]
|
|
2930
|
+
* @param {Boolean} [mirror=0] */
|
|
2931
|
+
drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color, angle, mirror)
|
|
2988
2932
|
{
|
|
2989
|
-
|
|
2990
|
-
if (tileData && (object ? object.collideWithTileRaycast(tileData, new Vector2(x, y)) : tileData > 0))
|
|
2933
|
+
this.drawCanvas2D(pos, size, angle, mirror, (context)=>
|
|
2991
2934
|
{
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
2935
|
+
if (tileIndex < 0)
|
|
2936
|
+
{
|
|
2937
|
+
// untextured
|
|
2938
|
+
context.fillStyle = color;
|
|
2939
|
+
context.fillRect(-.5, -.5, 1, 1);
|
|
2940
|
+
}
|
|
2941
|
+
else
|
|
2942
|
+
{
|
|
2943
|
+
const cols = tileImage.width/tileSize.x;
|
|
2944
|
+
context.globalAlpha = color.a; // only alpha, no color, is supported in this mode
|
|
2945
|
+
context.drawImage(tileImage,
|
|
2946
|
+
(tileIndex%cols)*tileSize.x, (tileIndex/cols|0)*tileSize.y,
|
|
2947
|
+
tileSize.x, tileSize.y, -.5, -.5, 1, 1);
|
|
2948
|
+
}
|
|
2949
|
+
});
|
|
3002
2950
|
}
|
|
3003
|
-
debugRaycast && debugLine(posStart, posEnd, '#00f',.02, 1);
|
|
3004
|
-
}
|
|
3005
2951
|
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
*
|
|
3016
|
-
* const color = randColor();
|
|
3017
|
-
* const data = new TileLayerData(tileIndex, direction, mirror, color);
|
|
2952
|
+
/** Draw a rectangle directly onto the layer canvas
|
|
2953
|
+
* @param {Vector2} pos
|
|
2954
|
+
* @param {Vector2} [size=Vector2(1,1)]
|
|
2955
|
+
* @param {Color} [color=Color()]
|
|
2956
|
+
* @param {Number} [angle=0] */
|
|
2957
|
+
drawRect(pos, size, color, angle)
|
|
2958
|
+
{ this.drawTile(pos, size, -1, 0, color, angle); }
|
|
2959
|
+
}
|
|
2960
|
+
/**
|
|
2961
|
+
* LittleJS Particle System
|
|
3018
2962
|
*/
|
|
3019
|
-
class TileLayerData
|
|
3020
|
-
{
|
|
3021
|
-
/** Create a tile layer data object, one for each tile in a TileLayer
|
|
3022
|
-
* @param {Number} [tile] - The tile to use, untextured if undefined
|
|
3023
|
-
* @param {Number} [direction=0] - Integer direction of tile, in 90 degree increments
|
|
3024
|
-
* @param {Boolean} [mirror=0] - If the tile should be mirrored along the x axis
|
|
3025
|
-
* @param {Color} [color=Color()] - Color of the tile */
|
|
3026
|
-
constructor(tile, direction=0, mirror=0, color=new Color())
|
|
3027
|
-
{
|
|
3028
|
-
/** @property {Number} - The tile to use, untextured if undefined */
|
|
3029
|
-
this.tile = tile;
|
|
3030
|
-
/** @property {Number} - Integer direction of tile, in 90 degree increments */
|
|
3031
|
-
this.direction = direction;
|
|
3032
|
-
/** @property {Boolean} - If the tile should be mirrored along the x axis */
|
|
3033
|
-
this.mirror = mirror;
|
|
3034
|
-
/** @property {Color} - Color of the tile */
|
|
3035
|
-
this.color = color;
|
|
3036
|
-
}
|
|
3037
2963
|
|
|
3038
|
-
|
|
3039
|
-
clear() { this.tile = this.direction = this.mirror = 0; color = new Color; }
|
|
3040
|
-
}
|
|
2964
|
+
'use strict';
|
|
3041
2965
|
|
|
3042
2966
|
/**
|
|
3043
|
-
*
|
|
3044
|
-
* - Each Tile layer is rendered to an off screen canvas
|
|
3045
|
-
* - To allow dynamic modifications, layers are rendered using canvas 2d
|
|
3046
|
-
* - Some devices like mobile phones are limited to 4k texture resolution
|
|
3047
|
-
* - So with 16x16 tiles this limits layers to 256x256 on mobile devices
|
|
2967
|
+
* Particle Emitter - Spawns particles with the given settings
|
|
3048
2968
|
* @extends EngineObject
|
|
3049
2969
|
* @example
|
|
3050
|
-
* // create
|
|
3051
|
-
*
|
|
3052
|
-
*
|
|
2970
|
+
* // create a particle emitter
|
|
2971
|
+
* let pos = vec2(2,3);
|
|
2972
|
+
* let particleEmiter = new ParticleEmitter
|
|
2973
|
+
* (
|
|
2974
|
+
* pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emiteCone
|
|
2975
|
+
* 0, vec2(16), // tileIndex, tileSize
|
|
2976
|
+
* new Color(1,1,1), new Color(0,0,0), // colorStartA, colorStartB
|
|
2977
|
+
* new Color(1,1,1,0), new Color(0,0,0,0), // colorEndA, colorEndB
|
|
2978
|
+
* 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
|
|
2979
|
+
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
|
|
2980
|
+
* .5, 1 // randomness, collide, additive, randomColorLinear, renderOrder
|
|
2981
|
+
* );
|
|
3053
2982
|
*/
|
|
3054
|
-
class
|
|
2983
|
+
class ParticleEmitter extends EngineObject
|
|
3055
2984
|
{
|
|
3056
|
-
/** Create a
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
2985
|
+
/** Create a particle system with the given settings
|
|
2986
|
+
* @param {Vector2} position - World space position of the emitter
|
|
2987
|
+
* @param {Number} [angle=0] - Angle to emit the particles
|
|
2988
|
+
* @param {Number} [emitSize=0] - World space size of the emitter (float for circle diameter, vec2 for rect)
|
|
2989
|
+
* @param {Number} [emitTime=0] - How long to stay alive (0 is forever)
|
|
2990
|
+
* @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0
|
|
2991
|
+
* @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter
|
|
2992
|
+
* @param {Number} [tileIndex=-1] - Index into tile sheet, if <0 no texture is applied
|
|
2993
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Tile size for particles
|
|
2994
|
+
* @param {Color} [colorStartA=Color()] - Color at start of life 1, randomized between start colors
|
|
2995
|
+
* @param {Color} [colorStartB=Color()] - Color at start of life 2, randomized between start colors
|
|
2996
|
+
* @param {Color} [colorEndA=Color(1,1,1,0)] - Color at end of life 1, randomized between end colors
|
|
2997
|
+
* @param {Color} [colorEndB=Color(1,1,1,0)] - Color at end of life 2, randomized between end colors
|
|
2998
|
+
* @param {Number} [particleTime=.5] - How long particles live
|
|
2999
|
+
* @param {Number} [sizeStart=.1] - How big are particles at start
|
|
3000
|
+
* @param {Number} [sizeEnd=1] - How big are particles at end
|
|
3001
|
+
* @param {Number} [speed=.1] - How fast are particles when spawned
|
|
3002
|
+
* @param {Number} [angleSpeed=.05] - How fast are particles rotating
|
|
3003
|
+
* @param {Number} [damping=1] - How much to dampen particle speed
|
|
3004
|
+
* @param {Number} [angleDamping=1] - How much to dampen particle angular speed
|
|
3005
|
+
* @param {Number} [gravityScale=0] - How much does gravity effect particles
|
|
3006
|
+
* @param {Number} [particleConeAngle=PI] - Cone for start particle angle
|
|
3007
|
+
* @param {Number} [fadeRate=.1] - How quick to fade in particles at start/end in percent of life
|
|
3008
|
+
* @param {Number} [randomness=.2] - Apply extra randomness percent
|
|
3009
|
+
* @param {Boolean} [collideTiles=0] - Do particles collide against tiles
|
|
3010
|
+
* @param {Boolean} [additive=0] - Should particles use addtive blend
|
|
3011
|
+
* @param {Boolean} [randomColorLinear=1] - Should color be randomized linearly or across each component
|
|
3012
|
+
* @param {Number} [renderOrder=0] - Render order for particles (additive is above other stuff by default)
|
|
3013
|
+
* @param {Boolean} [localSpace=0] - Should it be in local space of emitter (world space is default)
|
|
3014
|
+
*/
|
|
3015
|
+
constructor
|
|
3016
|
+
(
|
|
3017
|
+
pos,
|
|
3018
|
+
angle,
|
|
3019
|
+
emitSize = 0,
|
|
3020
|
+
emitTime = 0,
|
|
3021
|
+
emitRate = 100,
|
|
3022
|
+
emitConeAngle = PI,
|
|
3023
|
+
tileIndex = -1,
|
|
3024
|
+
tileSize = tileSizeDefault,
|
|
3025
|
+
colorStartA = new Color,
|
|
3026
|
+
colorStartB = new Color,
|
|
3027
|
+
colorEndA = new Color(1,1,1,0),
|
|
3028
|
+
colorEndB = new Color(1,1,1,0),
|
|
3029
|
+
particleTime = .5,
|
|
3030
|
+
sizeStart = .1,
|
|
3031
|
+
sizeEnd = 1,
|
|
3032
|
+
speed = .1,
|
|
3033
|
+
angleSpeed = .05,
|
|
3034
|
+
damping = 1,
|
|
3035
|
+
angleDamping = 1,
|
|
3036
|
+
gravityScale = 0,
|
|
3037
|
+
particleConeAngle = PI,
|
|
3038
|
+
fadeRate = .1,
|
|
3039
|
+
randomness = .2,
|
|
3040
|
+
collideTiles,
|
|
3041
|
+
additive,
|
|
3042
|
+
randomColorLinear = 1,
|
|
3043
|
+
renderOrder = additive ? 1e9 : 0,
|
|
3044
|
+
localSpace
|
|
3045
|
+
)
|
|
3064
3046
|
{
|
|
3065
|
-
super(pos,
|
|
3047
|
+
super(pos, vec2(), tileIndex, tileSize, angle, undefined, renderOrder);
|
|
3066
3048
|
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3049
|
+
// emitter settings
|
|
3050
|
+
/** @property {Number} - World space size of the emitter (float for circle diameter, vec2 for rect) */
|
|
3051
|
+
this.emitSize = emitSize
|
|
3052
|
+
/** @property {Number} - How long to stay alive (0 is forever) */
|
|
3053
|
+
this.emitTime = emitTime;
|
|
3054
|
+
/** @property {Number} - How many particles per second to spawn, does not emit if 0 */
|
|
3055
|
+
this.emitRate = emitRate;
|
|
3056
|
+
/** @property {Number} - Local angle to apply velocity to particles from emitter */
|
|
3057
|
+
this.emitConeAngle = emitConeAngle;
|
|
3075
3058
|
|
|
3076
|
-
//
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
{
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3059
|
+
// color settings
|
|
3060
|
+
/** @property {Color} - Color at start of life 1, randomized between start colors */
|
|
3061
|
+
this.colorStartA = colorStartA;
|
|
3062
|
+
/** @property {Color} - Color at start of life 2, randomized between start colors */
|
|
3063
|
+
this.colorStartB = colorStartB;
|
|
3064
|
+
/** @property {Color} - Color at end of life 1, randomized between end colors */
|
|
3065
|
+
this.colorEndA = colorEndA;
|
|
3066
|
+
/** @property {Color} - Color at end of life 2, randomized between end colors */
|
|
3067
|
+
this.colorEndB = colorEndB;
|
|
3068
|
+
/** @property {Boolean} - Should color be randomized linearly or across each component */
|
|
3069
|
+
this.randomColorLinear = randomColorLinear;
|
|
3070
|
+
|
|
3071
|
+
// particle settings
|
|
3072
|
+
/** @property {Number} - How long particles live */
|
|
3073
|
+
this.particleTime = particleTime;
|
|
3074
|
+
/** @property {Number} - How big are particles at start */
|
|
3075
|
+
this.sizeStart = sizeStart;
|
|
3076
|
+
/** @property {Number} - How big are particles at end */
|
|
3077
|
+
this.sizeEnd = sizeEnd;
|
|
3078
|
+
/** @property {Number} - How fast are particles when spawned */
|
|
3079
|
+
this.speed = speed;
|
|
3080
|
+
/** @property {Number} - How fast are particles rotating */
|
|
3081
|
+
this.angleSpeed = angleSpeed;
|
|
3082
|
+
/** @property {Number} - How much to dampen particle speed */
|
|
3083
|
+
this.damping = damping;
|
|
3084
|
+
/** @property {Number} - How much to dampen particle angular speed */
|
|
3085
|
+
this.angleDamping = angleDamping;
|
|
3086
|
+
/** @property {Number} - How much does gravity effect particles */
|
|
3087
|
+
this.gravityScale = gravityScale;
|
|
3088
|
+
/** @property {Number} - Cone for start particle angle */
|
|
3089
|
+
this.particleConeAngle = particleConeAngle;
|
|
3090
|
+
/** @property {Number} - How quick to fade in particles at start/end in percent of life */
|
|
3091
|
+
this.fadeRate = fadeRate;
|
|
3092
|
+
/** @property {Number} - Apply extra randomness percent */
|
|
3093
|
+
this.randomness = randomness;
|
|
3094
|
+
/** @property {Number} - Do particles collide against tiles */
|
|
3095
|
+
this.collideTiles = collideTiles;
|
|
3096
|
+
/** @property {Number} - Should particles use addtive blend */
|
|
3097
|
+
this.additive = additive;
|
|
3098
|
+
/** @property {Boolean} - Should it be in local space of emitter */
|
|
3099
|
+
this.localSpace = localSpace;
|
|
3100
|
+
/** @property {Number} - If set the partile is drawn as a trail, stretched in the drection of velocity */
|
|
3101
|
+
this.trailScale = 0;
|
|
3102
|
+
|
|
3103
|
+
// internal variables
|
|
3104
|
+
this.emitTimeBuffer = 0;
|
|
3093
3105
|
}
|
|
3094
3106
|
|
|
3095
|
-
/**
|
|
3096
|
-
|
|
3097
|
-
* @return {TileLayerData} */
|
|
3098
|
-
getData(layerPos)
|
|
3099
|
-
{ return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0]; }
|
|
3100
|
-
|
|
3101
|
-
// Tile layers are not updated
|
|
3102
|
-
update() {}
|
|
3103
|
-
|
|
3104
|
-
// Render the tile layer, called automatically by the engine
|
|
3105
|
-
render()
|
|
3107
|
+
/** Update the emitter to spawn particles, called automatically by engine once each frame */
|
|
3108
|
+
update()
|
|
3106
3109
|
{
|
|
3107
|
-
|
|
3110
|
+
// only do default update to apply parent transforms
|
|
3111
|
+
this.parent && super.update();
|
|
3108
3112
|
|
|
3109
|
-
//
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3113
|
+
// update emitter
|
|
3114
|
+
if (!this.emitTime || this.getAliveTime() <= this.emitTime)
|
|
3115
|
+
{
|
|
3116
|
+
// emit particles
|
|
3117
|
+
if (this.emitRate * particleEmitRateScale)
|
|
3118
|
+
{
|
|
3119
|
+
const rate = 1/this.emitRate/particleEmitRateScale;
|
|
3120
|
+
for (this.emitTimeBuffer += timeDelta; this.emitTimeBuffer > 0; this.emitTimeBuffer -= rate)
|
|
3121
|
+
this.emitParticle();
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
else
|
|
3125
|
+
this.destroy();
|
|
3120
3126
|
|
|
3121
|
-
|
|
3122
|
-
* - This may be slow in some browsers
|
|
3123
|
-
*/
|
|
3124
|
-
redraw()
|
|
3125
|
-
{
|
|
3126
|
-
this.redrawStart(1);
|
|
3127
|
-
this.drawAllTileData();
|
|
3128
|
-
this.redrawEnd();
|
|
3127
|
+
debugParticles && debugRect(this.pos, vec2(this.emitSize), '#0f0', 0, this.angle);
|
|
3129
3128
|
}
|
|
3130
3129
|
|
|
3131
|
-
/**
|
|
3132
|
-
* @
|
|
3133
|
-
|
|
3130
|
+
/** Spawn one particle
|
|
3131
|
+
* @return {Particle} */
|
|
3132
|
+
emitParticle()
|
|
3134
3133
|
{
|
|
3135
|
-
//
|
|
3136
|
-
this.
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
cameraScale = this.tileSize.x;
|
|
3143
|
-
|
|
3144
|
-
if (clear)
|
|
3134
|
+
// spawn a particle
|
|
3135
|
+
let pos = this.emitSize.x != undefined ? // check if vec2 was used for size
|
|
3136
|
+
vec2(rand(-.5,.5), rand(-.5,.5))
|
|
3137
|
+
.multiply(this.emitSize).rotate(this.angle) // box emitter
|
|
3138
|
+
: randInCircle(this.emitSize/2); // circle emitter
|
|
3139
|
+
let angle = rand(this.particleConeAngle, -this.particleConeAngle);
|
|
3140
|
+
if (!this.localSpace)
|
|
3145
3141
|
{
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
mainCanvas.height = this.size.y * this.tileSize.y;
|
|
3142
|
+
pos = this.pos.add(pos);
|
|
3143
|
+
angle += this.angle;
|
|
3149
3144
|
}
|
|
3145
|
+
|
|
3146
|
+
const particle = new Particle(pos, this.tileIndex, this.tileSize, angle);
|
|
3150
3147
|
|
|
3151
|
-
//
|
|
3152
|
-
|
|
3153
|
-
|
|
3148
|
+
// randomness scales each paremeter by a percentage
|
|
3149
|
+
const randomness = this.randomness;
|
|
3150
|
+
const randomizeScale = (v)=> v + v*rand(randomness, -randomness);
|
|
3154
3151
|
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3152
|
+
// randomize particle settings
|
|
3153
|
+
const particleTime = randomizeScale(this.particleTime);
|
|
3154
|
+
const sizeStart = randomizeScale(this.sizeStart);
|
|
3155
|
+
const sizeEnd = randomizeScale(this.sizeEnd);
|
|
3156
|
+
const speed = randomizeScale(this.speed);
|
|
3157
|
+
const angleSpeed = randomizeScale(this.angleSpeed) * randSign();
|
|
3158
|
+
const coneAngle = rand(this.emitConeAngle, -this.emitConeAngle);
|
|
3159
|
+
const colorStart = randColor(this.colorStartA, this.colorStartB, this.randomColorLinear);
|
|
3160
|
+
const colorEnd = randColor(this.colorEndA, this.colorEndB, this.randomColorLinear);
|
|
3161
|
+
const velocityAngle = this.localSpace ? coneAngle : this.angle + coneAngle;
|
|
3161
3162
|
|
|
3162
|
-
//
|
|
3163
|
-
|
|
3164
|
-
|
|
3163
|
+
// build particle settings
|
|
3164
|
+
particle.colorStart = colorStart;
|
|
3165
|
+
particle.colorEndDelta = colorEnd.subtract(colorStart);
|
|
3166
|
+
particle.velocity = vec2().setAngle(velocityAngle, speed);
|
|
3167
|
+
particle.angleVelocity = angleSpeed;
|
|
3168
|
+
particle.lifeTime = particleTime;
|
|
3169
|
+
particle.sizeStart = sizeStart;
|
|
3170
|
+
particle.sizeEndDelta = sizeEnd - sizeStart;
|
|
3171
|
+
particle.fadeRate = this.fadeRate;
|
|
3172
|
+
particle.damping = this.damping;
|
|
3173
|
+
particle.angleDamping = this.angleDamping;
|
|
3174
|
+
particle.elasticity = this.elasticity;
|
|
3175
|
+
particle.friction = this.friction;
|
|
3176
|
+
particle.gravityScale = this.gravityScale;
|
|
3177
|
+
particle.collideTiles = this.collideTiles;
|
|
3178
|
+
particle.additive = this.additive;
|
|
3179
|
+
particle.renderOrder = this.renderOrder;
|
|
3180
|
+
particle.trailScale = this.trailScale;
|
|
3181
|
+
particle.mirror = randInt(2);
|
|
3182
|
+
particle.localSpaceEmitter = this.localSpace && this;
|
|
3165
3183
|
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
{
|
|
3170
|
-
// first clear out where the tile was
|
|
3171
|
-
const pos = layerPos.floor().add(this.pos).add(vec2(.5));
|
|
3172
|
-
this.drawCanvas2D(pos, vec2(1), 0, 0, (context)=>context.clearRect(-.5, -.5, 1, 1));
|
|
3184
|
+
// setup callbacks for particles
|
|
3185
|
+
particle.destroyCallback = this.particleDestroyCallback;
|
|
3186
|
+
this.particleCreateCallback && this.particleCreateCallback(particle);
|
|
3173
3187
|
|
|
3174
|
-
//
|
|
3175
|
-
|
|
3176
|
-
if (d.tile != undefined)
|
|
3177
|
-
{
|
|
3178
|
-
ASSERT(mainContext == this.context); // must call redrawStart() before drawing tiles
|
|
3179
|
-
drawTile(pos, vec2(1), d.tile, this.tileSize, d.color, d.direction*PI/2, d.mirror);
|
|
3180
|
-
}
|
|
3188
|
+
// return the newly created particle
|
|
3189
|
+
return particle;
|
|
3181
3190
|
}
|
|
3182
3191
|
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
for (let x = this.size.x; x--;)
|
|
3187
|
-
for (let y = this.size.y; y--;)
|
|
3188
|
-
this.drawTileData(vec2(x,y));
|
|
3189
|
-
}
|
|
3192
|
+
// Particle emitters are not rendered, only the particles are
|
|
3193
|
+
render() {}
|
|
3194
|
+
}
|
|
3190
3195
|
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
context.restore();
|
|
3208
|
-
}
|
|
3196
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3197
|
+
/**
|
|
3198
|
+
* Particle Object - Created automatically by Particle Emitters
|
|
3199
|
+
* @extends EngineObject
|
|
3200
|
+
*/
|
|
3201
|
+
class Particle extends EngineObject
|
|
3202
|
+
{
|
|
3203
|
+
/**
|
|
3204
|
+
* Create a particle with the given settings
|
|
3205
|
+
* @param {Vector2} position - World space position of the particle
|
|
3206
|
+
* @param {Number} [tileIndex=-1] - Tile to use to render, untextured if -1
|
|
3207
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
|
|
3208
|
+
* @param {Number} [angle=0] - Angle to rotate the particle
|
|
3209
|
+
*/
|
|
3210
|
+
constructor(pos, tileIndex, tileSize, angle)
|
|
3211
|
+
{ super(pos, vec2(), tileIndex, tileSize, angle); }
|
|
3209
3212
|
|
|
3210
|
-
/**
|
|
3211
|
-
|
|
3212
|
-
* @param {Vector2} [size=Vector2(1,1)]
|
|
3213
|
-
* @param {Number} [tileIndex=-1]
|
|
3214
|
-
* @param {Vector2} [tileSize=tileSizeDefault]
|
|
3215
|
-
* @param {Color} [color=Color()]
|
|
3216
|
-
* @param {Number} [angle=0]
|
|
3217
|
-
* @param {Boolean} [mirror=0] */
|
|
3218
|
-
drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color, angle, mirror)
|
|
3213
|
+
/** Render the particle, automatically called each frame, sorted by renderOrder */
|
|
3214
|
+
render()
|
|
3219
3215
|
{
|
|
3220
|
-
|
|
3216
|
+
// modulate size and color
|
|
3217
|
+
const p = min((time - this.spawnTime) / this.lifeTime, 1);
|
|
3218
|
+
const radius = this.sizeStart + p * this.sizeEndDelta;
|
|
3219
|
+
const size = vec2(radius);
|
|
3220
|
+
const fadeRate = this.fadeRate/2;
|
|
3221
|
+
const color = new Color(
|
|
3222
|
+
this.colorStart.r + p * this.colorEndDelta.r,
|
|
3223
|
+
this.colorStart.g + p * this.colorEndDelta.g,
|
|
3224
|
+
this.colorStart.b + p * this.colorEndDelta.b,
|
|
3225
|
+
(this.colorStart.a + p * this.colorEndDelta.a) *
|
|
3226
|
+
(p < fadeRate ? p/fadeRate : p > 1-fadeRate ? (1-p)/fadeRate : 1)); // fade alpha
|
|
3227
|
+
|
|
3228
|
+
// draw the particle
|
|
3229
|
+
this.additive && setBlendMode(1);
|
|
3230
|
+
|
|
3231
|
+
let pos = this.pos, angle = this.angle;
|
|
3232
|
+
if (this.localSpaceEmitter)
|
|
3221
3233
|
{
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3234
|
+
// in local space of emitter
|
|
3235
|
+
pos = this.localSpaceEmitter.pos.add(pos.rotate(-this.localSpaceEmitter.angle));
|
|
3236
|
+
angle += this.localSpaceEmitter.angle;
|
|
3237
|
+
}
|
|
3238
|
+
if (this.trailScale)
|
|
3239
|
+
{
|
|
3240
|
+
// trail style particles
|
|
3241
|
+
let velocity = this.velocity;
|
|
3242
|
+
if (this.localSpaceEmitter)
|
|
3243
|
+
velocity = velocity.rotate(-this.localSpaceEmitter.angle);
|
|
3244
|
+
const speed = velocity.length();
|
|
3245
|
+
const direction = velocity.scale(1/speed);
|
|
3246
|
+
const trailLength = speed * this.trailScale;
|
|
3247
|
+
size.y = max(size.x, trailLength);
|
|
3248
|
+
angle = direction.angle();
|
|
3249
|
+
drawTile(pos.add(direction.multiply(vec2(0,-trailLength/2))), size, this.tileIndex, this.tileSize, color, angle, this.mirror);
|
|
3250
|
+
}
|
|
3251
|
+
else
|
|
3252
|
+
drawTile(pos, size, this.tileIndex, this.tileSize, color, angle, this.mirror);
|
|
3253
|
+
this.additive && setBlendMode();
|
|
3254
|
+
debugParticles && debugRect(pos, size, '#f005', 0, angle);
|
|
3238
3255
|
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
}
|
|
3256
|
+
if (p == 1)
|
|
3257
|
+
{
|
|
3258
|
+
// destroy particle when it's time runs out
|
|
3259
|
+
this.color = color;
|
|
3260
|
+
this.size = size;
|
|
3261
|
+
this.destroyCallback && this.destroyCallback(this);
|
|
3262
|
+
this.destroyed = 1;
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3247
3266
|
/**
|
|
3248
|
-
* LittleJS
|
|
3267
|
+
* LittleJS Medal System
|
|
3268
|
+
* - Tracks and displays medals
|
|
3269
|
+
* - Saves medals to local storage
|
|
3270
|
+
* - Newgrounds integration
|
|
3271
|
+
* @namespace Medals
|
|
3249
3272
|
*/
|
|
3250
3273
|
|
|
3251
3274
|
'use strict';
|
|
3252
3275
|
|
|
3253
|
-
/**
|
|
3254
|
-
*
|
|
3255
|
-
*
|
|
3276
|
+
/** List of all medals
|
|
3277
|
+
* @type {Array}
|
|
3278
|
+
* @memberof Medals */
|
|
3279
|
+
const medals = [];
|
|
3280
|
+
|
|
3281
|
+
// Engine internal variables not exposed to documentation
|
|
3282
|
+
let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimeLast;
|
|
3283
|
+
|
|
3284
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3285
|
+
|
|
3286
|
+
/** Initialize medals with a save name used for storage
|
|
3287
|
+
* - Call this after creating all medals
|
|
3288
|
+
* - Checks if medals are unlocked
|
|
3289
|
+
* @param {String} saveName
|
|
3290
|
+
* @memberof Medals */
|
|
3291
|
+
function medalsInit(saveName)
|
|
3292
|
+
{
|
|
3293
|
+
// check if medals are unlocked
|
|
3294
|
+
medalsSaveName = saveName;
|
|
3295
|
+
debugMedals || medals.forEach(medal=> medal.unlocked = (localStorage[medal.storageKey()] | 0));
|
|
3296
|
+
}
|
|
3297
|
+
|
|
3298
|
+
/**
|
|
3299
|
+
* Medal Object - Tracks an unlockable medal
|
|
3256
3300
|
* @example
|
|
3257
|
-
* // create a
|
|
3258
|
-
*
|
|
3259
|
-
*
|
|
3260
|
-
*
|
|
3261
|
-
*
|
|
3262
|
-
*
|
|
3263
|
-
*
|
|
3264
|
-
*
|
|
3265
|
-
* 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
|
|
3266
|
-
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
|
|
3267
|
-
* .5, 1 // randomness, collide, additive, randomColorLinear, renderOrder
|
|
3268
|
-
* );
|
|
3301
|
+
* // create a medal
|
|
3302
|
+
* const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️');
|
|
3303
|
+
*
|
|
3304
|
+
* // initialize medals
|
|
3305
|
+
* medalsInit('Example Game');
|
|
3306
|
+
*
|
|
3307
|
+
* // unlock the medal
|
|
3308
|
+
* medal_example.unlock();
|
|
3269
3309
|
*/
|
|
3270
|
-
class
|
|
3310
|
+
class Medal
|
|
3271
3311
|
{
|
|
3272
|
-
/** Create
|
|
3273
|
-
* @param {
|
|
3274
|
-
* @param {
|
|
3275
|
-
* @param {
|
|
3276
|
-
* @param {
|
|
3277
|
-
* @param {
|
|
3278
|
-
* @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter
|
|
3279
|
-
* @param {Number} [tileIndex=-1] - Index into tile sheet, if <0 no texture is applied
|
|
3280
|
-
* @param {Vector2} [tileSize=tileSizeDefault] - Tile size for particles
|
|
3281
|
-
* @param {Color} [colorStartA=Color()] - Color at start of life 1, randomized between start colors
|
|
3282
|
-
* @param {Color} [colorStartB=Color()] - Color at start of life 2, randomized between start colors
|
|
3283
|
-
* @param {Color} [colorEndA=Color(1,1,1,0)] - Color at end of life 1, randomized between end colors
|
|
3284
|
-
* @param {Color} [colorEndB=Color(1,1,1,0)] - Color at end of life 2, randomized between end colors
|
|
3285
|
-
* @param {Number} [particleTime=.5] - How long particles live
|
|
3286
|
-
* @param {Number} [sizeStart=.1] - How big are particles at start
|
|
3287
|
-
* @param {Number} [sizeEnd=1] - How big are particles at end
|
|
3288
|
-
* @param {Number} [speed=.1] - How fast are particles when spawned
|
|
3289
|
-
* @param {Number} [angleSpeed=.05] - How fast are particles rotating
|
|
3290
|
-
* @param {Number} [damping=1] - How much to dampen particle speed
|
|
3291
|
-
* @param {Number} [angleDamping=1] - How much to dampen particle angular speed
|
|
3292
|
-
* @param {Number} [gravityScale=0] - How much does gravity effect particles
|
|
3293
|
-
* @param {Number} [particleConeAngle=PI] - Cone for start particle angle
|
|
3294
|
-
* @param {Number} [fadeRate=.1] - How quick to fade in particles at start/end in percent of life
|
|
3295
|
-
* @param {Number} [randomness=.2] - Apply extra randomness percent
|
|
3296
|
-
* @param {Boolean} [collideTiles=0] - Do particles collide against tiles
|
|
3297
|
-
* @param {Boolean} [additive=0] - Should particles use addtive blend
|
|
3298
|
-
* @param {Boolean} [randomColorLinear=1] - Should color be randomized linearly or across each component
|
|
3299
|
-
* @param {Number} [renderOrder=0] - Render order for particles (additive is above other stuff by default)
|
|
3300
|
-
* @param {Boolean} [localSpace=0] - Should it be in local space of emitter (world space is default)
|
|
3312
|
+
/** Create an medal object and adds it to the list of medals
|
|
3313
|
+
* @param {Number} id - The unique identifier of the medal
|
|
3314
|
+
* @param {String} name - Name of the medal
|
|
3315
|
+
* @param {String} [description] - Description of the medal
|
|
3316
|
+
* @param {String} [icon='🏆'] - Icon for the medal
|
|
3317
|
+
* @param {String} [src] - Image location for the medal
|
|
3301
3318
|
*/
|
|
3302
|
-
constructor
|
|
3303
|
-
(
|
|
3304
|
-
pos,
|
|
3305
|
-
angle,
|
|
3306
|
-
emitSize = 0,
|
|
3307
|
-
emitTime = 0,
|
|
3308
|
-
emitRate = 100,
|
|
3309
|
-
emitConeAngle = PI,
|
|
3310
|
-
tileIndex = -1,
|
|
3311
|
-
tileSize = tileSizeDefault,
|
|
3312
|
-
colorStartA = new Color,
|
|
3313
|
-
colorStartB = new Color,
|
|
3314
|
-
colorEndA = new Color(1,1,1,0),
|
|
3315
|
-
colorEndB = new Color(1,1,1,0),
|
|
3316
|
-
particleTime = .5,
|
|
3317
|
-
sizeStart = .1,
|
|
3318
|
-
sizeEnd = 1,
|
|
3319
|
-
speed = .1,
|
|
3320
|
-
angleSpeed = .05,
|
|
3321
|
-
damping = 1,
|
|
3322
|
-
angleDamping = 1,
|
|
3323
|
-
gravityScale = 0,
|
|
3324
|
-
particleConeAngle = PI,
|
|
3325
|
-
fadeRate = .1,
|
|
3326
|
-
randomness = .2,
|
|
3327
|
-
collideTiles,
|
|
3328
|
-
additive,
|
|
3329
|
-
randomColorLinear = 1,
|
|
3330
|
-
renderOrder = additive ? 1e9 : 0,
|
|
3331
|
-
localSpace
|
|
3332
|
-
)
|
|
3319
|
+
constructor(id, name, description='', icon='🏆', src)
|
|
3333
3320
|
{
|
|
3334
|
-
|
|
3321
|
+
ASSERT(id >= 0 && !medals[id]);
|
|
3335
3322
|
|
|
3336
|
-
//
|
|
3337
|
-
|
|
3338
|
-
this.
|
|
3339
|
-
|
|
3340
|
-
this.
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
this.emitConeAngle = emitConeAngle;
|
|
3323
|
+
// save attributes and add to list of medals
|
|
3324
|
+
medals[this.id = id] = this;
|
|
3325
|
+
this.name = name;
|
|
3326
|
+
this.description = description;
|
|
3327
|
+
this.icon = icon;
|
|
3328
|
+
if (src)
|
|
3329
|
+
(this.image = new Image).src = src;
|
|
3330
|
+
}
|
|
3345
3331
|
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
/** @property {Color} - Color at end of life 1, randomized between end colors */
|
|
3352
|
-
this.colorEndA = colorEndA;
|
|
3353
|
-
/** @property {Color} - Color at end of life 2, randomized between end colors */
|
|
3354
|
-
this.colorEndB = colorEndB;
|
|
3355
|
-
/** @property {Boolean} - Should color be randomized linearly or across each component */
|
|
3356
|
-
this.randomColorLinear = randomColorLinear;
|
|
3332
|
+
/** Unlocks a medal if not already unlocked */
|
|
3333
|
+
unlock()
|
|
3334
|
+
{
|
|
3335
|
+
if (medalsPreventUnlock || this.unlocked)
|
|
3336
|
+
return;
|
|
3357
3337
|
|
|
3358
|
-
//
|
|
3359
|
-
|
|
3360
|
-
this.
|
|
3361
|
-
|
|
3362
|
-
this.
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
this.
|
|
3387
|
-
|
|
3388
|
-
this.
|
|
3338
|
+
// save the medal
|
|
3339
|
+
ASSERT(medalsSaveName); // save name must be set
|
|
3340
|
+
localStorage[this.storageKey()] = this.unlocked = 1;
|
|
3341
|
+
medalsDisplayQueue.push(this);
|
|
3342
|
+
newgrounds && newgrounds.unlockMedal(this.id);
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3345
|
+
/** Render a medal
|
|
3346
|
+
* @param {Number} [hidePercent=0] - How much to slide the medal off screen
|
|
3347
|
+
*/
|
|
3348
|
+
render(hidePercent=0)
|
|
3349
|
+
{
|
|
3350
|
+
const context = overlayContext;
|
|
3351
|
+
const width = min(medalDisplaySize.x, mainCanvas.width);
|
|
3352
|
+
const x = overlayCanvas.width - width;
|
|
3353
|
+
const y = -medalDisplaySize.y*hidePercent;
|
|
3354
|
+
|
|
3355
|
+
// draw containing rect and clip to that region
|
|
3356
|
+
context.save();
|
|
3357
|
+
context.beginPath();
|
|
3358
|
+
context.fillStyle = new Color(.9,.9,.9);
|
|
3359
|
+
context.strokeStyle = new Color(0,0,0);
|
|
3360
|
+
context.lineWidth = 3;
|
|
3361
|
+
context.fill(context.rect(x, y, width, medalDisplaySize.y));
|
|
3362
|
+
context.stroke();
|
|
3363
|
+
context.clip();
|
|
3364
|
+
|
|
3365
|
+
// draw the icon and text
|
|
3366
|
+
this.renderIcon(vec2(x+15+medalDisplayIconSize/2, y+medalDisplaySize.y/2));
|
|
3367
|
+
const pos = vec2(x+medalDisplayIconSize+30, y+28);
|
|
3368
|
+
drawTextScreen(this.name, pos, 38, new Color(0,0,0), 0, 0, 'left');
|
|
3369
|
+
pos.y += 32;
|
|
3370
|
+
drawTextScreen(this.description, pos, 24, new Color(0,0,0), 0, 0, 'left');
|
|
3371
|
+
context.restore();
|
|
3372
|
+
}
|
|
3373
|
+
|
|
3374
|
+
/** Render the icon for a medal
|
|
3375
|
+
* @param {Number} x - Screen space X position
|
|
3376
|
+
* @param {Number} y - Screen space Y position
|
|
3377
|
+
* @param {Number} [size=medalDisplayIconSize] - Screen space size
|
|
3378
|
+
*/
|
|
3379
|
+
renderIcon(pos, size=medalDisplayIconSize)
|
|
3380
|
+
{
|
|
3381
|
+
// draw the image or icon
|
|
3382
|
+
if (this.image)
|
|
3383
|
+
overlayContext.drawImage(this.image, pos.x-size/2, pos.y-size/2, size, size);
|
|
3384
|
+
else
|
|
3385
|
+
drawTextScreen(this.icon, pos, size*.7, new Color(0,0,0));
|
|
3386
|
+
}
|
|
3387
|
+
|
|
3388
|
+
// Get local storage key used by the medal
|
|
3389
|
+
storageKey() { return medalsSaveName + '_' + this.id; }
|
|
3390
|
+
}
|
|
3389
3391
|
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3392
|
+
// engine automatically renders medals
|
|
3393
|
+
function medalsRender()
|
|
3394
|
+
{
|
|
3395
|
+
if (!medalsDisplayQueue.length)
|
|
3396
|
+
return;
|
|
3393
3397
|
|
|
3394
|
-
|
|
3395
|
-
|
|
3398
|
+
// update first medal in queue
|
|
3399
|
+
const medal = medalsDisplayQueue[0];
|
|
3400
|
+
const time = timeReal - medalsDisplayTimeLast;
|
|
3401
|
+
if (!medalsDisplayTimeLast)
|
|
3402
|
+
medalsDisplayTimeLast = timeReal;
|
|
3403
|
+
else if (time > medalDisplayTime)
|
|
3404
|
+
medalsDisplayQueue.shift(medalsDisplayTimeLast = 0);
|
|
3405
|
+
else
|
|
3396
3406
|
{
|
|
3397
|
-
//
|
|
3398
|
-
|
|
3407
|
+
// slide on/off medals
|
|
3408
|
+
const slideOffTime = medalDisplayTime - medalDisplaySlideTime;
|
|
3409
|
+
const hidePercent =
|
|
3410
|
+
time < medalDisplaySlideTime ? 1 - time / medalDisplaySlideTime :
|
|
3411
|
+
time > slideOffTime ? (time - slideOffTime) / medalDisplaySlideTime : 0;
|
|
3412
|
+
medal.render(hidePercent);
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3399
3415
|
|
|
3400
|
-
|
|
3401
|
-
|
|
3416
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3417
|
+
|
|
3418
|
+
// global Newgrounds object
|
|
3419
|
+
let newgrounds;
|
|
3420
|
+
|
|
3421
|
+
/** This can used to enable Newgrounds functionality
|
|
3422
|
+
* @param {Number} app_id - The newgrounds App ID
|
|
3423
|
+
* @param {String} [cipher] - The encryption Key (AES-128/Base64)
|
|
3424
|
+
* @memberof Medals */
|
|
3425
|
+
function newgroundsInit(app_id, cipher) { newgrounds = new Newgrounds(app_id, cipher); }
|
|
3426
|
+
|
|
3427
|
+
/**
|
|
3428
|
+
* Newgrounds API wrapper object
|
|
3429
|
+
* @example
|
|
3430
|
+
* // create a newgrounds object, replace the app id and cipher with your own
|
|
3431
|
+
* const app_id = '53123:1ZuSTQ9l';
|
|
3432
|
+
* const cipher = 'enF0vGH@Mj/FRASKL23Q==';
|
|
3433
|
+
* newgrounds = new Newgrounds(app_id, cipher);
|
|
3434
|
+
*/
|
|
3435
|
+
class Newgrounds
|
|
3436
|
+
{
|
|
3437
|
+
/** Create a newgrounds object
|
|
3438
|
+
* @param {Number} app_id - The newgrounds App ID
|
|
3439
|
+
* @param {String} [cipher] - The encryption Key (AES-128/Base64) */
|
|
3440
|
+
constructor(app_id, cipher)
|
|
3441
|
+
{
|
|
3442
|
+
ASSERT(!newgrounds && app_id);
|
|
3443
|
+
|
|
3444
|
+
this.app_id = app_id;
|
|
3445
|
+
this.cipher = cipher;
|
|
3446
|
+
this.host = location ? location.hostname : '';
|
|
3447
|
+
|
|
3448
|
+
// create an instance of CryptoJS for encrypted calls
|
|
3449
|
+
if (cipher)
|
|
3450
|
+
this.cryptoJS = this.CryptoJS();
|
|
3451
|
+
|
|
3452
|
+
// get session id from url search params
|
|
3453
|
+
const url = new URL(location.href);
|
|
3454
|
+
this.session_id = url.searchParams.get('ngio_session_id');
|
|
3455
|
+
|
|
3456
|
+
if (!this.session_id)
|
|
3457
|
+
return; // only use newgrounds when logged in
|
|
3458
|
+
|
|
3459
|
+
// get medals
|
|
3460
|
+
const medalsResult = this.call('Medal.getList');
|
|
3461
|
+
this.medals = medalsResult ? medalsResult.result.data['medals'] : [];
|
|
3462
|
+
debugMedals && console.log(this.medals);
|
|
3463
|
+
for (const newgroundsMedal of this.medals)
|
|
3402
3464
|
{
|
|
3403
|
-
|
|
3404
|
-
if (
|
|
3465
|
+
const medal = medals[newgroundsMedal['id']];
|
|
3466
|
+
if (medal)
|
|
3405
3467
|
{
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3468
|
+
// copy newgrounds medal data
|
|
3469
|
+
medal.image = new Image;
|
|
3470
|
+
medal.image.src = newgroundsMedal['icon'];
|
|
3471
|
+
medal.name = newgroundsMedal['name'];
|
|
3472
|
+
medal.description = newgroundsMedal['description'];
|
|
3473
|
+
medal.unlocked = newgroundsMedal['unlocked'];
|
|
3474
|
+
medal.difficulty = newgroundsMedal['difficulty'];
|
|
3475
|
+
medal.value = newgroundsMedal['value'];
|
|
3476
|
+
|
|
3477
|
+
if (medal.value)
|
|
3478
|
+
medal.description = medal.description + ' (' + medal.value + ')';
|
|
3409
3479
|
}
|
|
3410
3480
|
}
|
|
3411
|
-
|
|
3412
|
-
|
|
3481
|
+
|
|
3482
|
+
// get scoreboards
|
|
3483
|
+
const scoreboardResult = this.call('ScoreBoard.getBoards');
|
|
3484
|
+
this.scoreboards = scoreboardResult ? scoreboardResult.result.data.scoreboards : [];
|
|
3485
|
+
debugMedals && console.log(this.scoreboards);
|
|
3413
3486
|
|
|
3414
|
-
|
|
3487
|
+
const keepAliveMS = 5 * 60 * 1e3;
|
|
3488
|
+
setInterval(()=>this.call('Gateway.ping', 0, 1), keepAliveMS);
|
|
3415
3489
|
}
|
|
3416
3490
|
|
|
3417
|
-
/**
|
|
3418
|
-
*
|
|
3419
|
-
|
|
3491
|
+
/** Send message to unlock a medal by id
|
|
3492
|
+
* @param {Number} id - The medal id */
|
|
3493
|
+
unlockMedal(id) { return this.call('Medal.unlock', {'id':id}, 1); }
|
|
3494
|
+
|
|
3495
|
+
/** Send message to post score
|
|
3496
|
+
* @param {Number} id - The scoreboard id
|
|
3497
|
+
* @param {Number} value - The score value */
|
|
3498
|
+
postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, 1); }
|
|
3499
|
+
|
|
3500
|
+
/** Get scores from a scoreboard
|
|
3501
|
+
* @param {Number} id - The scoreboard id
|
|
3502
|
+
* @param {String} [user=0] - A user's id or name
|
|
3503
|
+
* @param {Number} [social=0] - If true, only social scores will be loaded
|
|
3504
|
+
* @param {Number} [skip=0] - Number of scores to skip before start
|
|
3505
|
+
* @param {Number} [limit=10] - Number of scores to include in the list
|
|
3506
|
+
* @return {Object} - The response JSON object
|
|
3507
|
+
*/
|
|
3508
|
+
getScores(id, user=0, social=0, skip=0, limit=10)
|
|
3509
|
+
{ return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
|
|
3510
|
+
|
|
3511
|
+
/** Send message to log a view */
|
|
3512
|
+
logView() { return this.call('App.logView', {'host':this.host}, 1); }
|
|
3513
|
+
|
|
3514
|
+
/** Send a message to call a component of the Newgrounds API
|
|
3515
|
+
* @param {String} component - Name of the component
|
|
3516
|
+
* @param {Object} [parameters=0] - Parameters to use for call
|
|
3517
|
+
* @param {Boolean} [async=0] - If true, don't wait for response before continuing (avoid stall)
|
|
3518
|
+
* @return {Object} - The response JSON object
|
|
3519
|
+
*/
|
|
3520
|
+
call(component, parameters=0, async=0)
|
|
3420
3521
|
{
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
(new Vector2(rand(-.5,.5), rand(-.5,.5)))
|
|
3424
|
-
.multiply(this.emitSize).rotate(this.angle) // box emitter
|
|
3425
|
-
: randInCircle(this.emitSize/2); // circle emitter
|
|
3426
|
-
let angle = rand(this.particleConeAngle, -this.particleConeAngle);
|
|
3427
|
-
if (!this.localSpace)
|
|
3522
|
+
const call = {'component':component, 'parameters':parameters};
|
|
3523
|
+
if (this.cipher)
|
|
3428
3524
|
{
|
|
3429
|
-
|
|
3430
|
-
|
|
3525
|
+
// encrypt using AES-128 Base64 with cryptoJS
|
|
3526
|
+
const cryptoJS = this.cryptoJS;
|
|
3527
|
+
const aesKey = cryptoJS['enc']['Base64']['parse'](this.cipher);
|
|
3528
|
+
const iv = cryptoJS['lib']['WordArray']['random'](16);
|
|
3529
|
+
const encrypted = cryptoJS['AES']['encrypt'](JSON.stringify(call), aesKey, {'iv':iv});
|
|
3530
|
+
call['secure'] = cryptoJS['enc']['Base64']['stringify'](iv.concat(encrypted['ciphertext']));
|
|
3531
|
+
call['parameters'] = 0;
|
|
3431
3532
|
}
|
|
3432
|
-
|
|
3433
|
-
const particle = new Particle(pos, this.tileIndex, this.tileSize, angle);
|
|
3434
3533
|
|
|
3435
|
-
//
|
|
3436
|
-
const
|
|
3437
|
-
|
|
3534
|
+
// build the input object
|
|
3535
|
+
const input =
|
|
3536
|
+
{
|
|
3537
|
+
'app_id': this.app_id,
|
|
3538
|
+
'session_id': this.session_id,
|
|
3539
|
+
'call': call
|
|
3540
|
+
};
|
|
3438
3541
|
|
|
3439
|
-
//
|
|
3440
|
-
const
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
const
|
|
3445
|
-
const
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3542
|
+
// build post data
|
|
3543
|
+
const formData = new FormData();
|
|
3544
|
+
formData.append('input', JSON.stringify(input));
|
|
3545
|
+
|
|
3546
|
+
// send post data
|
|
3547
|
+
const xmlHttp = new XMLHttpRequest();
|
|
3548
|
+
const url = 'https://newgrounds.io/gateway_v3.php';
|
|
3549
|
+
xmlHttp.open('POST', url, !debugMedals && async);
|
|
3550
|
+
xmlHttp.send(formData);
|
|
3551
|
+
debugMedals && console.log(xmlHttp.responseText);
|
|
3552
|
+
return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
|
|
3553
|
+
}
|
|
3449
3554
|
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3555
|
+
CryptoJS()
|
|
3556
|
+
{
|
|
3557
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3558
|
+
// Crypto-JS - https://github.com/brix/crypto-js - MIT License
|
|
3559
|
+
//
|
|
3560
|
+
// [The MIT License (MIT)](http://opensource.org/licenses/MIT)
|
|
3561
|
+
//
|
|
3562
|
+
// Copyright (c) 2009-2013 Jeff Mott
|
|
3563
|
+
// Copyright (c) 2013-2016 Evan Vosberg
|
|
3564
|
+
//
|
|
3565
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
3566
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
3567
|
+
// in the Software without restriction, including without limitation the rights
|
|
3568
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
3569
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
3570
|
+
// furnished to do so, subject to the following conditions:
|
|
3571
|
+
//
|
|
3572
|
+
// The above copyright notice and this permission notice shall be included in
|
|
3573
|
+
// all copies or substantial portions of the Software.
|
|
3574
|
+
//
|
|
3575
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
3576
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
3577
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
3578
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
3579
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
3580
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
3581
|
+
// THE SOFTWARE.
|
|
3582
|
+
return eval(Function("[M='GBMGXz^oVYPPKKbB`agTXU|LxPc_ZBcMrZvCr~wyGfWrwk@ATqlqeTp^N?p{we}jIpEnB_sEr`l?YDkDhWhprc|Er|XETG?pTl`e}dIc[_N~}fzRycIfpW{HTolvoPB_FMe_eH~BTMx]yyOhv?biWPCGc]kABencBhgERHGf{OL`Dj`c^sh@canhy[secghiyotcdOWgO{tJIE^JtdGQRNSCrwKYciZOa]Y@tcRATYKzv|sXpboHcbCBf`}SKeXPFM|RiJsSNaIb]QPc[D]Jy_O^XkOVTZep`ONmntLL`Qz~UupHBX_Ia~WX]yTRJIxG`ioZ{fefLJFhdyYoyLPvqgH?b`[TMnTwwfzDXhfM?rKs^aFr|nyBdPmVHTtAjXoYUloEziWDCw_suyYT~lSMksI~ZNCS[Bex~j]Vz?kx`gdYSEMCsHpjbyxQvw|XxX_^nQYue{sBzVWQKYndtYQMWRef{bOHSfQhiNdtR{o?cUAHQAABThwHPT}F{VvFmgN`E@FiFYS`UJmpQNM`X|tPKHlccT}z}k{sACHL?Rt@MkWplxO`ASgh?hBsuuP|xD~LSH~KBlRs]t|l|_tQAroDRqWS^SEr[sYdPB}TAROtW{mIkE|dWOuLgLmJrucGLpebrAFKWjikTUzS|j}M}szasKOmrjy[?hpwnEfX[jGpLt@^v_eNwSQHNwtOtDgWD{rk|UgASs@mziIXrsHN_|hZuxXlPJOsA^^?QY^yGoCBx{ekLuZzRqQZdsNSx@ezDAn{XNj@fRXIwrDX?{ZQHwTEfu@GhxDOykqts|n{jOeZ@c`dvTY?e^]ATvWpb?SVyg]GC?SlzteilZJAL]mlhLjYZazY__qcVFYvt@|bIQnSno@OXyt]OulzkWqH`rYFWrwGs`v|~XeTsIssLrbmHZCYHiJrX}eEzSssH}]l]IhPQhPoQ}rCXLyhFIT[clhzYOvyHqigxmjz`phKUU^TPf[GRAIhNqSOdayFP@FmKmuIzMOeoqdpxyCOwCthcLq?n`L`tLIBboNn~uXeFcPE{C~mC`h]jUUUQe^`UqvzCutYCgct|SBrAeiYQW?X~KzCz}guXbsUw?pLsg@hDArw?KeJD[BN?GD@wgFWCiHq@Ypp_QKFixEKWqRp]oJFuVIEvjDcTFu~Zz]a{IcXhWuIdMQjJ]lwmGQ|]g~c]Hl]pl`Pd^?loIcsoNir_kikBYyg?NarXZEGYspt_vLBIoj}LI[uBFvm}tbqvC|xyR~a{kob|HlctZslTGtPDhBKsNsoZPuH`U`Fqg{gKnGSHVLJ^O`zmNgMn~{rsQuoymw^JY?iUBvw_~mMr|GrPHTERS[MiNpY[Mm{ggHpzRaJaoFomtdaQ_?xuTRm}@KjU~RtPsAdxa|uHmy}n^i||FVL[eQAPrWfLm^ndczgF~Nk~aplQvTUpHvnTya]kOenZlLAQIm{lPl@CCTchvCF[fI{^zPkeYZTiamoEcKmBMfZhk_j_~Fjp|wPVZlkh_nHu]@tP|hS@^G^PdsQ~f[RqgTDqezxNFcaO}HZhb|MMiNSYSAnQWCDJukT~e|OTgc}sf[cnr?fyzTa|EwEtRG|I~|IO}O]S|rp]CQ}}DWhSjC_|z|oY|FYl@WkCOoPuWuqr{fJu?Brs^_EBI[@_OCKs}?]O`jnDiXBvaIWhhMAQDNb{U`bqVR}oqVAvR@AZHEBY@depD]OLh`kf^UsHhzKT}CS}HQKy}Q~AeMydXPQztWSSzDnghULQgMAmbWIZ|lWWeEXrE^EeNoZApooEmrXe{NAnoDf`m}UNlRdqQ@jOc~HLOMWs]IDqJHYoMziEedGBPOxOb?[X`KxkFRg@`mgFYnP{hSaxwZfBQqTm}_?RSEaQga]w[vxc]hMne}VfSlqUeMo_iqmd`ilnJXnhdj^EEFifvZyxYFRf^VaqBhLyrGlk~qowqzHOBlOwtx?i{m~`n^G?Yxzxux}b{LSlx]dS~thO^lYE}bzKmUEzwW^{rPGhbEov[Plv??xtyKJshbG`KuO?hjBdS@Ru}iGpvFXJRrvOlrKN?`I_n_tplk}kgwSXuKylXbRQ]]?a|{xiT[li?k]CJpwy^o@ebyGQrPfF`aszGKp]baIx~H?ElETtFh]dz[OjGl@C?]VDhr}OE@V]wLTc[WErXacM{We`F|utKKjgllAxvsVYBZ@HcuMgLboFHVZmi}eIXAIFhS@A@FGRbjeoJWZ_NKd^oEH`qgy`q[Tq{x?LRP|GfBFFJV|fgZs`MLbpPYUdIV^]mD@FG]pYAT^A^RNCcXVrPsgk{jTrAIQPs_`mD}rOqAZA[}RETFz]WkXFTz_m{N@{W@_fPKZLT`@aIqf|L^Mb|crNqZ{BVsijzpGPEKQQZGlApDn`ruH}cvF|iXcNqK}cxe_U~HRnKV}sCYb`D~oGvwG[Ca|UaybXea~DdD~LiIbGRxJ_VGheI{ika}KC[OZJLn^IBkPrQj_EuoFwZ}DpoBRcK]Q}?EmTv~i_Tul{bky?Iit~tgS|o}JL_VYcCQdjeJ_MfaA`FgCgc[Ii|CBHwq~nbJeYTK{e`CNstKfTKPzw{jdhp|qsZyP_FcugxCFNpKitlR~vUrx^NrSVsSTaEgnxZTmKc`R|lGJeX}ccKLsQZQhsFkeFd|ckHIVTlGMg`~uPwuHRJS_CPuN_ogXe{Ba}dO_UBhuNXby|h?JlgBIqMKx^_u{molgL[W_iavNQuOq?ap]PGB`clAicnl@k~pA?MWHEZ{HuTLsCpOxxrKlBh]FyMjLdFl|nMIvTHyGAlPogqfZ?PlvlFJvYnDQd}R@uAhtJmDfe|iJqdkYr}r@mEjjIetDl_I`TELfoR|qTBu@Tic[BaXjP?dCS~MUK[HPRI}OUOwAaf|_}HZzrwXvbnNgltjTwkBE~MztTQhtRSWoQHajMoVyBBA`kdgK~h`o[J`dm~pm]tk@i`[F~F]DBlJKklrkR]SNw@{aG~Vhl`KINsQkOy?WhcqUMTGDOM_]bUjVd|Yh_KUCCgIJ|LDIGZCPls{RzbVWVLEhHvWBzKq|^N?DyJB|__aCUjoEgsARki}j@DQXS`RNU|DJ^a~d{sh_Iu{ONcUtSrGWW@cvUjefHHi}eSSGrNtO?cTPBShLqzwMVjWQQCCFB^culBjZHEK_{dO~Q`YhJYFn]jq~XSnG@[lQr]eKrjXpG~L^h~tDgEma^AUFThlaR{xyuP@[^VFwXSeUbVetufa@dX]CLyAnDV@Bs[DnpeghJw^?UIana}r_CKGDySoRudklbgio}kIDpA@McDoPK?iYcG?_zOmnWfJp}a[JLR[stXMo?_^Ng[whQlrDbrawZeSZ~SJstIObdDSfAA{MV}?gNunLOnbMv_~KFQUAjIMj^GkoGxuYtYbGDImEYiwEMyTpMxN_LSnSMdl{bg@dtAnAMvhDTBR_FxoQgANniRqxd`pWv@rFJ|mWNWmh[GMJz_Nq`BIN@KsjMPASXORcdHjf~rJfgZYe_uulzqM_KdPlMsuvU^YJuLtofPhGonVOQxCMuXliNvJIaoC?hSxcxKVVxWlNs^ENDvCtSmO~WxI[itnjs^RDvI@KqG}YekaSbTaB]ki]XM@[ZnDAP~@|BzLRgOzmjmPkRE@_sobkT|SszXK[rZN?F]Z_u}Yue^[BZgLtR}FHzWyxWEX^wXC]MJmiVbQuBzkgRcKGUhOvUc_bga|Tx`KEM`JWEgTpFYVeXLCm|mctZR@uKTDeUONPozBeIkrY`cz]]~WPGMUf`MNUGHDbxZuO{gmsKYkAGRPqjc|_FtblEOwy}dnwCHo]PJhN~JoteaJ?dmYZeB^Xd?X^pOKDbOMF@Ugg^hETLdhwlA}PL@_ur|o{VZosP?ntJ_kG][g{Zq`Tu]dzQlSWiKfnxDnk}KOzp~tdFstMobmy[oPYjyOtUzMWdjcNSUAjRuqhLS@AwB^{BFnqjCmmlk?jpn}TksS{KcKkDboXiwK]qMVjm~V`LgWhjS^nLGwfhAYrjDSBL_{cRus~{?xar_xqPlArrYFd?pHKdMEZzzjJpfC?Hv}mAuIDkyBxFpxhstTx`IO{rp}XGuQ]VtbHerlRc_LFGWK[XluFcNGUtDYMZny[M^nVKVeMllQI[xtvwQnXFlWYqxZZFp_|]^oWX[{pOMpxXxvkbyJA[DrPzwD|LW|QcV{Nw~U^dgguSpG]ClmO@j_TENIGjPWwgdVbHganhM?ema|dBaqla|WBd`poj~klxaasKxGG^xbWquAl~_lKWxUkDFagMnE{zHug{b`A~IYcQYBF_E}wiA}K@yxWHrZ{[d~|ARsYsjeNWzkMs~IOqqp[yzDE|WFrivsidTcnbHFRoW@XpAV`lv_zj?B~tPCppRjgbbDTALeFaOf?VcjnKTQMLyp{NwdylHCqmo?oelhjWuXj~}{fpuX`fra?GNkDiChYgVSh{R[BgF~eQa^WVz}ATI_CpY?g_diae]|ijH`TyNIF}|D_xpmBq_JpKih{Ba|sWzhnAoyraiDvk`h{qbBfsylBGmRH}DRPdryEsSaKS~tIaeF[s]I~xxHVrcNe@Jjxa@jlhZueLQqHh_]twVMqG_EGuwyab{nxOF?`HCle}nBZzlTQjkLmoXbXhOtBglFoMz?eqre`HiE@vNwBulglmQjj]DB@pPkPUgA^sjOAUNdSu_`oAzar?n?eMnw{{hYmslYi[TnlJD'",...']charCodeAtUinyxpf',"for(;e<10359;c[e++]=p-=128,A=A?p-A&&A:p==34&&p)for(p=1;p<128;y=f.map((n,x)=>(U=r[n]*2+1,U=Math.log(U/(h-U)),t-=a[x]*U,U/500)),t=~-h/(1+Math.exp(t))|1,i=o%h<t,o=o%h+(i?t:h-t)*(o>>17)-!i*t,f.map((n,x)=>(U=r[n]+=(i*h/2-r[n]<<13)/((C[n]+=C[n]<5)+1/20)>>13,a[x]+=y[x]*(i-t/h))),p=p*2+i)for(f='010202103203210431053105410642065206541'.split(t=0).map((n,x)=>(U=0,[...n].map((n,x)=>(U=U*997+(c[e-n]|0)|0)),h*32-1&U*997+p+!!A*129)*12+x);o<h*32;o=o*64|M.charCodeAt(d++)&63);for(C=String.fromCharCode(...c);r=/[\0-#?@\\\\~]/.exec(C);)with(C.split(r))C=join(shift());return C")([],[],1<<17,[0,0,0,0,0,0,0,0,0,0,0,0],new Uint16Array(51e6).fill(1<<15),new Uint8Array(51e6),0,0,0,0));
|
|
3583
|
+
// end of Crypto-JS
|
|
3584
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3585
|
+
}
|
|
3586
|
+
}
|
|
3587
|
+
/**
|
|
3588
|
+
* LittleJS WebGL Interface
|
|
3589
|
+
* - All webgl used by the engine is wrapped up here
|
|
3590
|
+
* - For normal stuff you won't need to see or call anything in this file
|
|
3591
|
+
* - For advanced stuff there are helper functions to create shaders, textures, etc
|
|
3592
|
+
* - Can be disabled with glEnable to revert to 2D canvas rendering
|
|
3593
|
+
* - Batches sprite rendering on GPU for incredibly fast performance
|
|
3594
|
+
* - Sprite transform math is done in the shader where possible
|
|
3595
|
+
* @namespace WebGL
|
|
3596
|
+
*/
|
|
3597
|
+
|
|
3598
|
+
'use strict';
|
|
3599
|
+
|
|
3600
|
+
/** The WebGL canvas which appears above the main canvas and below the overlay canvas
|
|
3601
|
+
* @type {HTMLCanvasElement}
|
|
3602
|
+
* @memberof WebGL */
|
|
3603
|
+
let glCanvas;
|
|
3470
3604
|
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3605
|
+
/** 2d context for glCanvas
|
|
3606
|
+
* @type {WebGLRenderingContext}
|
|
3607
|
+
* @memberof WebGL */
|
|
3608
|
+
let glContext;
|
|
3474
3609
|
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3610
|
+
/** Main tile sheet texture automatically loaded by engine
|
|
3611
|
+
* @type {WebGLTexture}
|
|
3612
|
+
* @memberof WebGL */
|
|
3613
|
+
let glTileTexture;
|
|
3478
3614
|
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
}
|
|
3615
|
+
// WebGL internal variables not exposed to documentation
|
|
3616
|
+
let glActiveTexture, glShader, glArrayBuffer, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
|
|
3482
3617
|
|
|
3483
3618
|
///////////////////////////////////////////////////////////////////////////////
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
*/
|
|
3488
|
-
class Particle extends EngineObject
|
|
3619
|
+
|
|
3620
|
+
// Init WebGL, called automatically by the engine
|
|
3621
|
+
function glInit()
|
|
3489
3622
|
{
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
|
|
3495
|
-
* @param {Number} [angle=0] - Angle to rotate the particle
|
|
3496
|
-
*/
|
|
3497
|
-
constructor(pos, tileIndex, tileSize, angle)
|
|
3498
|
-
{ super(pos, new Vector2, tileIndex, tileSize, angle); }
|
|
3623
|
+
// create the canvas and tile texture
|
|
3624
|
+
glCanvas = document.createElement('canvas');
|
|
3625
|
+
glContext = glCanvas.getContext('webgl', {antialias: false});
|
|
3626
|
+
glTileTexture = glCreateTexture(tileImage);
|
|
3499
3627
|
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
{
|
|
3503
|
-
// modulate size and color
|
|
3504
|
-
const p = min((time - this.spawnTime) / this.lifeTime, 1);
|
|
3505
|
-
const radius = this.sizeStart + p * this.sizeEndDelta;
|
|
3506
|
-
const size = new Vector2(radius, radius);
|
|
3507
|
-
const fadeRate = this.fadeRate/2;
|
|
3508
|
-
const color = new Color(
|
|
3509
|
-
this.colorStart.r + p * this.colorEndDelta.r,
|
|
3510
|
-
this.colorStart.g + p * this.colorEndDelta.g,
|
|
3511
|
-
this.colorStart.b + p * this.colorEndDelta.b,
|
|
3512
|
-
(this.colorStart.a + p * this.colorEndDelta.a) *
|
|
3513
|
-
(p < fadeRate ? p/fadeRate : p > 1-fadeRate ? (1-p)/fadeRate : 1)); // fade alpha
|
|
3628
|
+
// some browsers are much faster without copying the gl buffer so we just overlay it instead
|
|
3629
|
+
glOverlay && document.body.appendChild(glCanvas);
|
|
3514
3630
|
|
|
3515
|
-
|
|
3516
|
-
|
|
3631
|
+
// setup vertex and fragment shaders
|
|
3632
|
+
glShader = glCreateProgram(
|
|
3633
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
3634
|
+
'uniform mat4 m;'+ // transform matrix
|
|
3635
|
+
'attribute vec2 p,t;'+ // position, uv
|
|
3636
|
+
'attribute vec4 c,a;'+ // color, additiveColor
|
|
3637
|
+
'varying vec4 v,d,e;'+ // return uv, color, additiveColor
|
|
3638
|
+
'void main(){'+ // shader entry point
|
|
3639
|
+
'gl_Position=m*vec4(p,1,1);'+ // transform position
|
|
3640
|
+
'v=vec4(t,p);d=c;e=a;'+ // pass stuff to fragment shader
|
|
3641
|
+
'}' // end of shader
|
|
3642
|
+
,
|
|
3643
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
3644
|
+
'varying vec4 v,d,e;'+ // uv, color, additiveColor
|
|
3645
|
+
'uniform sampler2D s;'+ // texture
|
|
3646
|
+
'void main(){'+ // shader entry point
|
|
3647
|
+
'gl_FragColor=texture2D(s,v.xy)*d+e;'+ // modulate texture by color plus additive
|
|
3648
|
+
'}' // end of shader
|
|
3649
|
+
);
|
|
3517
3650
|
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
if (this.trailScale)
|
|
3526
|
-
{
|
|
3527
|
-
// trail style particles
|
|
3528
|
-
let velocity = this.velocity;
|
|
3529
|
-
if (this.localSpaceEmitter)
|
|
3530
|
-
velocity = velocity.rotate(-this.localSpaceEmitter.angle);
|
|
3531
|
-
const speed = velocity.length();
|
|
3532
|
-
const direction = velocity.scale(1/speed);
|
|
3533
|
-
const trailLength = speed * this.trailScale;
|
|
3534
|
-
size.y = max(size.x, trailLength);
|
|
3535
|
-
angle = direction.angle();
|
|
3536
|
-
drawTile(pos.add(direction.multiply(vec2(0,-trailLength/2))), size, this.tileIndex, this.tileSize, color, angle, this.mirror);
|
|
3537
|
-
}
|
|
3538
|
-
else
|
|
3539
|
-
drawTile(pos, size, this.tileIndex, this.tileSize, color, angle, this.mirror);
|
|
3540
|
-
this.additive && setBlendMode();
|
|
3541
|
-
debugParticles && debugRect(pos, size, '#f005', 0, angle);
|
|
3651
|
+
// init buffers
|
|
3652
|
+
const vertexData = new ArrayBuffer(gl_VERTEX_BUFFER_SIZE);
|
|
3653
|
+
glArrayBuffer = glContext.createBuffer();
|
|
3654
|
+
glPositionData = new Float32Array(vertexData);
|
|
3655
|
+
glColorData = new Uint32Array(vertexData);
|
|
3656
|
+
glBatchCount = 0;
|
|
3657
|
+
}
|
|
3542
3658
|
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
}
|
|
3551
|
-
}
|
|
3659
|
+
/** Set the WebGl blend mode, normally you should call setBlendMode instead
|
|
3660
|
+
* @param {Boolean} [additive=0]
|
|
3661
|
+
* @memberof WebGL */
|
|
3662
|
+
function glSetBlendMode(additive)
|
|
3663
|
+
{
|
|
3664
|
+
// setup blending
|
|
3665
|
+
glAdditive = additive;
|
|
3552
3666
|
}
|
|
3553
|
-
/**
|
|
3554
|
-
* LittleJS Medal System
|
|
3555
|
-
* - Tracks and displays medals
|
|
3556
|
-
* - Saves medals to local storage
|
|
3557
|
-
* - Newgrounds integration
|
|
3558
|
-
* @namespace Medals
|
|
3559
|
-
*/
|
|
3560
3667
|
|
|
3561
|
-
|
|
3668
|
+
/** Set the WebGl texture, not normally necessary unless multiple tile sheets are used
|
|
3669
|
+
* - This may also flush the gl buffer resulting in more draw calls and worse performance
|
|
3670
|
+
* @param {WebGLTexture} [texture=glTileTexture]
|
|
3671
|
+
* @memberof WebGL */
|
|
3672
|
+
function glSetTexture(texture=glTileTexture)
|
|
3673
|
+
{
|
|
3674
|
+
// must flush cache with the old texture to set a new one
|
|
3675
|
+
if (texture != glActiveTexture)
|
|
3676
|
+
glFlush();
|
|
3562
3677
|
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
* @memberof Medals */
|
|
3566
|
-
const medals = [];
|
|
3678
|
+
glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = texture);
|
|
3679
|
+
}
|
|
3567
3680
|
|
|
3568
|
-
|
|
3569
|
-
|
|
3681
|
+
/** Compile WebGL shader of the given type, will throw errors if in debug mode
|
|
3682
|
+
* @param {String} source
|
|
3683
|
+
* @param type
|
|
3684
|
+
* @return {WebGLShader}
|
|
3685
|
+
* @memberof WebGL */
|
|
3686
|
+
function glCompileShader(source, type)
|
|
3687
|
+
{
|
|
3688
|
+
// build the shader
|
|
3689
|
+
const shader = glContext.createShader(type);
|
|
3690
|
+
glContext.shaderSource(shader, source);
|
|
3691
|
+
glContext.compileShader(shader);
|
|
3570
3692
|
|
|
3571
|
-
|
|
3693
|
+
// check for errors
|
|
3694
|
+
if (debug && !glContext.getShaderParameter(shader, gl_COMPILE_STATUS))
|
|
3695
|
+
throw glContext.getShaderInfoLog(shader);
|
|
3696
|
+
return shader;
|
|
3697
|
+
}
|
|
3572
3698
|
|
|
3573
|
-
/**
|
|
3574
|
-
*
|
|
3575
|
-
*
|
|
3576
|
-
* @
|
|
3577
|
-
* @memberof
|
|
3578
|
-
function
|
|
3699
|
+
/** Create WebGL program with given shaders
|
|
3700
|
+
* @param {WebGLShader} vsSource
|
|
3701
|
+
* @param {WebGLShader} fsSource
|
|
3702
|
+
* @return {WebGLProgram}
|
|
3703
|
+
* @memberof WebGL */
|
|
3704
|
+
function glCreateProgram(vsSource, fsSource)
|
|
3579
3705
|
{
|
|
3580
|
-
//
|
|
3581
|
-
|
|
3582
|
-
|
|
3706
|
+
// build the program
|
|
3707
|
+
const program = glContext.createProgram();
|
|
3708
|
+
glContext.attachShader(program, glCompileShader(vsSource, gl_VERTEX_SHADER));
|
|
3709
|
+
glContext.attachShader(program, glCompileShader(fsSource, gl_FRAGMENT_SHADER));
|
|
3710
|
+
glContext.linkProgram(program);
|
|
3711
|
+
|
|
3712
|
+
// check for errors
|
|
3713
|
+
if (debug && !glContext.getProgramParameter(program, gl_LINK_STATUS))
|
|
3714
|
+
throw glContext.getProgramInfoLog(program);
|
|
3715
|
+
return program;
|
|
3583
3716
|
}
|
|
3584
3717
|
|
|
3585
|
-
/**
|
|
3586
|
-
*
|
|
3587
|
-
*
|
|
3588
|
-
*
|
|
3589
|
-
|
|
3590
|
-
*
|
|
3591
|
-
* // initialize medals
|
|
3592
|
-
* medalsInit('Example Game');
|
|
3593
|
-
*
|
|
3594
|
-
* // unlock the medal
|
|
3595
|
-
* medal_example.unlock();
|
|
3596
|
-
*/
|
|
3597
|
-
class Medal
|
|
3718
|
+
/** Create WebGL texture from an image and set the texture settings
|
|
3719
|
+
* @param {Image} image
|
|
3720
|
+
* @return {WebGLTexture}
|
|
3721
|
+
* @memberof WebGL */
|
|
3722
|
+
function glCreateTexture(image)
|
|
3598
3723
|
{
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3724
|
+
// build the texture
|
|
3725
|
+
const texture = glContext.createTexture();
|
|
3726
|
+
glContext.bindTexture(gl_TEXTURE_2D, texture);
|
|
3727
|
+
image && image.width && glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
|
|
3728
|
+
|
|
3729
|
+
// use point filtering for pixelated rendering
|
|
3730
|
+
const filter = canvasPixelated ? gl_NEAREST : gl_LINEAR;
|
|
3731
|
+
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
|
|
3732
|
+
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, filter);
|
|
3733
|
+
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE);
|
|
3734
|
+
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE);
|
|
3735
|
+
return texture;
|
|
3736
|
+
}
|
|
3609
3737
|
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
(this.image = new Image).src = src;
|
|
3617
|
-
}
|
|
3738
|
+
// called automatically by engine before render
|
|
3739
|
+
function glPreRender()
|
|
3740
|
+
{
|
|
3741
|
+
// clear and set to same size as main canvas
|
|
3742
|
+
glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
|
|
3743
|
+
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
3618
3744
|
|
|
3619
|
-
|
|
3620
|
-
|
|
3745
|
+
// set up the shader
|
|
3746
|
+
glContext.useProgram(glShader);
|
|
3747
|
+
glContext.activeTexture(gl_TEXTURE0);
|
|
3748
|
+
glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
|
|
3749
|
+
glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
|
|
3750
|
+
glContext.bufferData(gl_ARRAY_BUFFER, gl_VERTEX_BUFFER_SIZE, gl_DYNAMIC_DRAW);
|
|
3751
|
+
glSetBlendMode();
|
|
3752
|
+
|
|
3753
|
+
// set vertex attributes
|
|
3754
|
+
let offset = 0;
|
|
3755
|
+
const initVertexAttribArray = (name, type, typeSize, size, normalize=0)=>
|
|
3621
3756
|
{
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
ASSERT(medalsSaveName); // save name must be set
|
|
3627
|
-
localStorage[this.storageKey()] = this.unlocked = 1;
|
|
3628
|
-
medalsDisplayQueue.push(this);
|
|
3629
|
-
newgrounds && newgrounds.unlockMedal(this.id);
|
|
3757
|
+
const location = glContext.getAttribLocation(glShader, name);
|
|
3758
|
+
glContext.enableVertexAttribArray(location);
|
|
3759
|
+
glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
|
|
3760
|
+
offset += size*typeSize;
|
|
3630
3761
|
}
|
|
3762
|
+
initVertexAttribArray('p', gl_FLOAT, 4, 2); // position
|
|
3763
|
+
initVertexAttribArray('t', gl_FLOAT, 4, 2); // texture coords
|
|
3764
|
+
initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
|
|
3765
|
+
initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
|
|
3631
3766
|
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3767
|
+
// build the transform matrix
|
|
3768
|
+
const sx = 2 * cameraScale / mainCanvas.width;
|
|
3769
|
+
const sy = 2 * cameraScale / mainCanvas.height;
|
|
3770
|
+
glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), 0,
|
|
3771
|
+
new Float32Array([
|
|
3772
|
+
sx, 0, 0, 0,
|
|
3773
|
+
0, sy, 0, 0,
|
|
3774
|
+
1, 1, -1, 1,
|
|
3775
|
+
-1-sx*cameraPos.x, -1-sy*cameraPos.y, 0, 0
|
|
3776
|
+
])
|
|
3777
|
+
);
|
|
3778
|
+
}
|
|
3641
3779
|
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
context.lineWidth = 3;
|
|
3648
|
-
context.fill(context.rect(x, y, width, medalDisplaySize.y));
|
|
3649
|
-
context.stroke();
|
|
3650
|
-
context.clip();
|
|
3780
|
+
/** Draw all sprites and clear out the buffer, called automatically by the system whenever necessary
|
|
3781
|
+
* @memberof WebGL */
|
|
3782
|
+
function glFlush()
|
|
3783
|
+
{
|
|
3784
|
+
if (!glBatchCount) return;
|
|
3651
3785
|
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
drawTextScreen(this.name, pos, 38, new Color(0,0,0), 0, 0, 'left');
|
|
3656
|
-
pos.y += 32;
|
|
3657
|
-
drawTextScreen(this.description, pos, 24, new Color(0,0,0), 0, 0, 'left');
|
|
3658
|
-
context.restore();
|
|
3659
|
-
}
|
|
3786
|
+
const destBlend = glBatchAdditive ? gl_ONE : gl_ONE_MINUS_SRC_ALPHA;
|
|
3787
|
+
glContext.blendFuncSeparate(gl_SRC_ALPHA, destBlend, gl_ONE, destBlend);
|
|
3788
|
+
glContext.enable(gl_BLEND);
|
|
3660
3789
|
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
{
|
|
3668
|
-
// draw the image or icon
|
|
3669
|
-
if (this.image)
|
|
3670
|
-
overlayContext.drawImage(this.image, pos.x-size/2, pos.y-size/2, size, size);
|
|
3671
|
-
else
|
|
3672
|
-
drawTextScreen(this.icon, pos, size*.7, new Color(0,0,0));
|
|
3673
|
-
}
|
|
3674
|
-
|
|
3675
|
-
// Get local storage key used by the medal
|
|
3676
|
-
storageKey() { return medalsSaveName + '_' + this.id; }
|
|
3790
|
+
// draw all the sprites in the batch and reset the buffer
|
|
3791
|
+
glContext.bufferSubData(gl_ARRAY_BUFFER, 0,
|
|
3792
|
+
glPositionData.subarray(0, glBatchCount * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT));
|
|
3793
|
+
glContext.drawArrays(gl_TRIANGLES, 0, glBatchCount * gl_VERTICES_PER_QUAD);
|
|
3794
|
+
glBatchCount = 0;
|
|
3795
|
+
glBatchAdditive = glAdditive;
|
|
3677
3796
|
}
|
|
3678
3797
|
|
|
3679
|
-
|
|
3680
|
-
|
|
3798
|
+
/** Draw any sprites still in the buffer, copy to main canvas and clear
|
|
3799
|
+
* @param {CanvasRenderingContext2D} context
|
|
3800
|
+
* @param {Boolean} [forceDraw=0]
|
|
3801
|
+
* @memberof WebGL */
|
|
3802
|
+
function glCopyToContext(context, forceDraw)
|
|
3681
3803
|
{
|
|
3682
|
-
if (!
|
|
3683
|
-
return;
|
|
3804
|
+
if (!glBatchCount && !forceDraw) return;
|
|
3684
3805
|
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
if (!
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3806
|
+
glFlush();
|
|
3807
|
+
|
|
3808
|
+
// do not draw in overlay mode because the canvas is visible
|
|
3809
|
+
if (!glOverlay || forceDraw)
|
|
3810
|
+
context.drawImage(glCanvas, 0, 0);
|
|
3811
|
+
}
|
|
3812
|
+
|
|
3813
|
+
/** Add a sprite to the gl draw list, used by all gl draw functions
|
|
3814
|
+
* @param x
|
|
3815
|
+
* @param y
|
|
3816
|
+
* @param sizeX
|
|
3817
|
+
* @param sizeY
|
|
3818
|
+
* @param angle
|
|
3819
|
+
* @param uv0X
|
|
3820
|
+
* @param uv0Y
|
|
3821
|
+
* @param uv1X
|
|
3822
|
+
* @param uv1Y
|
|
3823
|
+
* @param rgba
|
|
3824
|
+
* @param [rgbaAdditive=0]
|
|
3825
|
+
* @memberof WebGL */
|
|
3826
|
+
function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdditive=0)
|
|
3827
|
+
{
|
|
3828
|
+
// flush if there is no room for more verts or if different blend mode
|
|
3829
|
+
if (glBatchCount == gl_MAX_BATCH || glBatchAdditive != glAdditive)
|
|
3830
|
+
glFlush();
|
|
3831
|
+
|
|
3832
|
+
// prepare to create the verts from size and angle
|
|
3833
|
+
const c = Math.cos(angle)/2, s = Math.sin(angle)/2;
|
|
3834
|
+
const cx = c*sizeX, cy = c*sizeY, sx = s*sizeX, sy = s*sizeY;
|
|
3835
|
+
|
|
3836
|
+
// setup 2 triangles to form a quad
|
|
3837
|
+
for(let i=6, offset = glBatchCount++ * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT; i--;)
|
|
3693
3838
|
{
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3839
|
+
const a = i-4&&i>1, b = i-5&&i-2&&i-1;
|
|
3840
|
+
glPositionData[offset++] = x + (a?-cx:cx) + (b?sy:-sy);
|
|
3841
|
+
glPositionData[offset++] = y + (b?cy:-cy) + (a?sx:-sx);
|
|
3842
|
+
glPositionData[offset++] = a ? uv0X : uv1X;
|
|
3843
|
+
glPositionData[offset++] = b ? uv0Y : uv1Y;
|
|
3844
|
+
glColorData[offset++] = rgba;
|
|
3845
|
+
glColorData[offset++] = rgbaAdditive;
|
|
3700
3846
|
}
|
|
3701
3847
|
}
|
|
3702
3848
|
|
|
3703
3849
|
///////////////////////////////////////////////////////////////////////////////
|
|
3850
|
+
// post processing - can be enabled to pass other canvases through a final shader
|
|
3704
3851
|
|
|
3705
|
-
|
|
3706
|
-
let newgrounds;
|
|
3707
|
-
|
|
3708
|
-
/** This can used to enable Newgrounds functionality
|
|
3709
|
-
* @param {Number} app_id - The newgrounds App ID
|
|
3710
|
-
* @param {String} [cipher] - The encryption Key (AES-128/Base64)
|
|
3711
|
-
* @memberof Medals */
|
|
3712
|
-
function newgroundsInit(app_id, cipher) { newgrounds = new Newgrounds(app_id, cipher); }
|
|
3852
|
+
let glPostShader, glPostArrayBuffer, glPostTexture, glPostIncludeOverlay;
|
|
3713
3853
|
|
|
3714
|
-
/**
|
|
3715
|
-
*
|
|
3716
|
-
*
|
|
3717
|
-
*
|
|
3718
|
-
|
|
3719
|
-
* const cipher = 'enF0vGH@Mj/FRASKL23Q==';
|
|
3720
|
-
* newgrounds = new Newgrounds(app_id, cipher);
|
|
3721
|
-
*/
|
|
3722
|
-
class Newgrounds
|
|
3854
|
+
/** Set up a post processing shader
|
|
3855
|
+
* @param {String} shaderCode
|
|
3856
|
+
* @param {Boolean} includeOverlay
|
|
3857
|
+
* @memberof WebGL */
|
|
3858
|
+
function glInitPostProcess(shaderCode, includeOverlay)
|
|
3723
3859
|
{
|
|
3724
|
-
|
|
3725
|
-
* @param {Number} app_id - The newgrounds App ID
|
|
3726
|
-
* @param {String} [cipher] - The encryption Key (AES-128/Base64) */
|
|
3727
|
-
constructor(app_id, cipher)
|
|
3728
|
-
{
|
|
3729
|
-
ASSERT(!newgrounds && app_id);
|
|
3860
|
+
ASSERT(!glPostShader); // can only have 1 post effects shader
|
|
3730
3861
|
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
this.host = location ? location.hostname : '';
|
|
3734
|
-
|
|
3735
|
-
// create an instance of CryptoJS for encrypted calls
|
|
3736
|
-
if (cipher)
|
|
3737
|
-
this.cryptoJS = this.CryptoJS();
|
|
3862
|
+
if (!shaderCode) // default shader
|
|
3863
|
+
shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture2D(iChannel0,p/iResolution.xy);}';
|
|
3738
3864
|
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3865
|
+
// create the shader
|
|
3866
|
+
glPostShader = glCreateProgram(
|
|
3867
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
3868
|
+
'attribute vec2 p;'+ // position
|
|
3869
|
+
'void main(){'+ // shader entry point
|
|
3870
|
+
'gl_Position=vec4(p,1,1);'+ // set position
|
|
3871
|
+
'}' // end of shader
|
|
3872
|
+
,
|
|
3873
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
3874
|
+
'uniform sampler2D iChannel0;'+ // input texture
|
|
3875
|
+
'uniform vec3 iResolution;'+ // size of output texture
|
|
3876
|
+
'uniform float iTime;'+ // time passed
|
|
3877
|
+
'\n' + shaderCode + '\n'+ // insert custom shader code
|
|
3878
|
+
'void main(){'+ // shader entry point
|
|
3879
|
+
'mainImage(gl_FragColor,gl_FragCoord.xy);'+ // call post process function
|
|
3880
|
+
'gl_FragColor.a=1.;'+ // always use full alpha
|
|
3881
|
+
'}' // end of shader
|
|
3882
|
+
);
|
|
3742
3883
|
|
|
3743
|
-
|
|
3744
|
-
|
|
3884
|
+
// create buffer and texture
|
|
3885
|
+
glPostArrayBuffer = glContext.createBuffer();
|
|
3886
|
+
glPostTexture = glCreateTexture();
|
|
3887
|
+
glPostIncludeOverlay = includeOverlay;
|
|
3745
3888
|
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
debugMedals && console.log(this.medals);
|
|
3750
|
-
for (const newgroundsMedal of this.medals)
|
|
3751
|
-
{
|
|
3752
|
-
const medal = medals[newgroundsMedal['id']];
|
|
3753
|
-
if (medal)
|
|
3754
|
-
{
|
|
3755
|
-
// copy newgrounds medal data
|
|
3756
|
-
medal.image = new Image;
|
|
3757
|
-
medal.image.src = newgroundsMedal['icon'];
|
|
3758
|
-
medal.name = newgroundsMedal['name'];
|
|
3759
|
-
medal.description = newgroundsMedal['description'];
|
|
3760
|
-
medal.unlocked = newgroundsMedal['unlocked'];
|
|
3761
|
-
medal.difficulty = newgroundsMedal['difficulty'];
|
|
3762
|
-
medal.value = newgroundsMedal['value'];
|
|
3889
|
+
// hide the original 2d canvas
|
|
3890
|
+
mainCanvas.style.visibility = 'hidden';
|
|
3891
|
+
}
|
|
3763
3892
|
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3893
|
+
// Render the post processing shader, called automatically by the engine
|
|
3894
|
+
function glRenderPostProcess()
|
|
3895
|
+
{
|
|
3896
|
+
if (!glPostShader)
|
|
3897
|
+
return;
|
|
3768
3898
|
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
const keepAliveMS = 5 * 60 * 1e3;
|
|
3775
|
-
setInterval(()=>this.call('Gateway.ping', 0, 1), keepAliveMS);
|
|
3899
|
+
// prepare to render post process shader
|
|
3900
|
+
if (glEnable)
|
|
3901
|
+
{
|
|
3902
|
+
glFlush(); // clear out the buffer
|
|
3903
|
+
mainContext.drawImage(glCanvas, 0, 0); // copy to the main canvas
|
|
3776
3904
|
}
|
|
3905
|
+
else // set viewport
|
|
3906
|
+
glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
|
|
3777
3907
|
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3908
|
+
if (glPostIncludeOverlay)
|
|
3909
|
+
{
|
|
3910
|
+
// copy overlay canvas so it will be included in post processing
|
|
3911
|
+
mainContext.drawImage(overlayCanvas, 0, 0);
|
|
3781
3912
|
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, 1); }
|
|
3913
|
+
// clear overlay canvas
|
|
3914
|
+
overlayCanvas.width = mainCanvas.width;
|
|
3915
|
+
}
|
|
3786
3916
|
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
* @return {Object} - The response JSON object
|
|
3794
|
-
*/
|
|
3795
|
-
getScores(id, user=0, social=0, skip=0, limit=10)
|
|
3796
|
-
{ return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
|
|
3917
|
+
// setup shader program to draw one triangle
|
|
3918
|
+
glContext.useProgram(glPostShader);
|
|
3919
|
+
glContext.disable(gl_BLEND);
|
|
3920
|
+
glContext.bindBuffer(gl_ARRAY_BUFFER, glPostArrayBuffer);
|
|
3921
|
+
glContext.bufferData(gl_ARRAY_BUFFER, new Float32Array([-3,1,1,-3,1,1]), gl_STATIC_DRAW);
|
|
3922
|
+
glContext.pixelStorei(gl_UNPACK_FLIP_Y_WEBGL, true);
|
|
3797
3923
|
|
|
3798
|
-
|
|
3799
|
-
|
|
3924
|
+
// set textures, pass in the 2d canvas and gl canvas in separate texture channels
|
|
3925
|
+
glContext.activeTexture(gl_TEXTURE0);
|
|
3926
|
+
glContext.bindTexture(gl_TEXTURE_2D, glPostTexture);
|
|
3927
|
+
glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, mainCanvas);
|
|
3800
3928
|
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
*/
|
|
3807
|
-
call(component, parameters=0, async=0)
|
|
3808
|
-
{
|
|
3809
|
-
const call = {'component':component, 'parameters':parameters};
|
|
3810
|
-
if (this.cipher)
|
|
3811
|
-
{
|
|
3812
|
-
// encrypt using AES-128 Base64 with cryptoJS
|
|
3813
|
-
const cryptoJS = this.cryptoJS;
|
|
3814
|
-
const aesKey = cryptoJS['enc']['Base64']['parse'](this.cipher);
|
|
3815
|
-
const iv = cryptoJS['lib']['WordArray']['random'](16);
|
|
3816
|
-
const encrypted = cryptoJS['AES']['encrypt'](JSON.stringify(call), aesKey, {'iv':iv});
|
|
3817
|
-
call['secure'] = cryptoJS['enc']['Base64']['stringify'](iv.concat(encrypted['ciphertext']));
|
|
3818
|
-
call['parameters'] = 0;
|
|
3819
|
-
}
|
|
3929
|
+
// set vertex position attribute
|
|
3930
|
+
const vertexByteStride = 8;
|
|
3931
|
+
const pLocation = glContext.getAttribLocation(glPostShader, 'p');
|
|
3932
|
+
glContext.enableVertexAttribArray(pLocation);
|
|
3933
|
+
glContext.vertexAttribPointer(pLocation, 2, gl_FLOAT, 0, vertexByteStride, 0);
|
|
3820
3934
|
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3935
|
+
// set uniforms and draw
|
|
3936
|
+
const uniformLocation = (name)=>glContext.getUniformLocation(glPostShader, name);
|
|
3937
|
+
glContext.uniform1i(uniformLocation('iChannel0'), 0);
|
|
3938
|
+
glContext.uniform1f(uniformLocation('iTime'), time);
|
|
3939
|
+
glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
|
|
3940
|
+
glContext.drawArrays(gl_TRIANGLES, 0, 3);
|
|
3941
|
+
}
|
|
3828
3942
|
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3943
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3944
|
+
// store gl constants as integers so their name doesn't use space in minifed
|
|
3945
|
+
const
|
|
3946
|
+
gl_ONE = 1,
|
|
3947
|
+
gl_TRIANGLES = 4,
|
|
3948
|
+
gl_SRC_ALPHA = 770,
|
|
3949
|
+
gl_ONE_MINUS_SRC_ALPHA = 771,
|
|
3950
|
+
gl_BLEND = 3042,
|
|
3951
|
+
gl_TEXTURE_2D = 3553,
|
|
3952
|
+
gl_UNSIGNED_BYTE = 5121,
|
|
3953
|
+
gl_BYTE = 5120,
|
|
3954
|
+
gl_FLOAT = 5126,
|
|
3955
|
+
gl_RGBA = 6408,
|
|
3956
|
+
gl_NEAREST = 9728,
|
|
3957
|
+
gl_LINEAR = 9729,
|
|
3958
|
+
gl_TEXTURE_MAG_FILTER = 10240,
|
|
3959
|
+
gl_TEXTURE_MIN_FILTER = 10241,
|
|
3960
|
+
gl_TEXTURE_WRAP_S = 10242,
|
|
3961
|
+
gl_TEXTURE_WRAP_T = 10243,
|
|
3962
|
+
gl_COLOR_BUFFER_BIT = 16384,
|
|
3963
|
+
gl_CLAMP_TO_EDGE = 33071,
|
|
3964
|
+
gl_TEXTURE0 = 33984,
|
|
3965
|
+
gl_TEXTURE1 = 33985,
|
|
3966
|
+
gl_ARRAY_BUFFER = 34962,
|
|
3967
|
+
gl_STATIC_DRAW = 35044,
|
|
3968
|
+
gl_DYNAMIC_DRAW = 35048,
|
|
3969
|
+
gl_FRAGMENT_SHADER = 35632,
|
|
3970
|
+
gl_VERTEX_SHADER = 35633,
|
|
3971
|
+
gl_COMPILE_STATUS = 35713,
|
|
3972
|
+
gl_LINK_STATUS = 35714,
|
|
3973
|
+
gl_UNPACK_FLIP_Y_WEBGL = 37440,
|
|
3841
3974
|
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
//
|
|
3849
|
-
// Copyright (c) 2009-2013 Jeff Mott
|
|
3850
|
-
// Copyright (c) 2013-2016 Evan Vosberg
|
|
3851
|
-
//
|
|
3852
|
-
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
3853
|
-
// of this software and associated documentation files (the "Software"), to deal
|
|
3854
|
-
// in the Software without restriction, including without limitation the rights
|
|
3855
|
-
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
3856
|
-
// copies of the Software, and to permit persons to whom the Software is
|
|
3857
|
-
// furnished to do so, subject to the following conditions:
|
|
3858
|
-
//
|
|
3859
|
-
// The above copyright notice and this permission notice shall be included in
|
|
3860
|
-
// all copies or substantial portions of the Software.
|
|
3861
|
-
//
|
|
3862
|
-
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
3863
|
-
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
3864
|
-
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
3865
|
-
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
3866
|
-
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
3867
|
-
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
3868
|
-
// THE SOFTWARE.
|
|
3869
|
-
return eval(Function("[M='GBMGXz^oVYPPKKbB`agTXU|LxPc_ZBcMrZvCr~wyGfWrwk@ATqlqeTp^N?p{we}jIpEnB_sEr`l?YDkDhWhprc|Er|XETG?pTl`e}dIc[_N~}fzRycIfpW{HTolvoPB_FMe_eH~BTMx]yyOhv?biWPCGc]kABencBhgERHGf{OL`Dj`c^sh@canhy[secghiyotcdOWgO{tJIE^JtdGQRNSCrwKYciZOa]Y@tcRATYKzv|sXpboHcbCBf`}SKeXPFM|RiJsSNaIb]QPc[D]Jy_O^XkOVTZep`ONmntLL`Qz~UupHBX_Ia~WX]yTRJIxG`ioZ{fefLJFhdyYoyLPvqgH?b`[TMnTwwfzDXhfM?rKs^aFr|nyBdPmVHTtAjXoYUloEziWDCw_suyYT~lSMksI~ZNCS[Bex~j]Vz?kx`gdYSEMCsHpjbyxQvw|XxX_^nQYue{sBzVWQKYndtYQMWRef{bOHSfQhiNdtR{o?cUAHQAABThwHPT}F{VvFmgN`E@FiFYS`UJmpQNM`X|tPKHlccT}z}k{sACHL?Rt@MkWplxO`ASgh?hBsuuP|xD~LSH~KBlRs]t|l|_tQAroDRqWS^SEr[sYdPB}TAROtW{mIkE|dWOuLgLmJrucGLpebrAFKWjikTUzS|j}M}szasKOmrjy[?hpwnEfX[jGpLt@^v_eNwSQHNwtOtDgWD{rk|UgASs@mziIXrsHN_|hZuxXlPJOsA^^?QY^yGoCBx{ekLuZzRqQZdsNSx@ezDAn{XNj@fRXIwrDX?{ZQHwTEfu@GhxDOykqts|n{jOeZ@c`dvTY?e^]ATvWpb?SVyg]GC?SlzteilZJAL]mlhLjYZazY__qcVFYvt@|bIQnSno@OXyt]OulzkWqH`rYFWrwGs`v|~XeTsIssLrbmHZCYHiJrX}eEzSssH}]l]IhPQhPoQ}rCXLyhFIT[clhzYOvyHqigxmjz`phKUU^TPf[GRAIhNqSOdayFP@FmKmuIzMOeoqdpxyCOwCthcLq?n`L`tLIBboNn~uXeFcPE{C~mC`h]jUUUQe^`UqvzCutYCgct|SBrAeiYQW?X~KzCz}guXbsUw?pLsg@hDArw?KeJD[BN?GD@wgFWCiHq@Ypp_QKFixEKWqRp]oJFuVIEvjDcTFu~Zz]a{IcXhWuIdMQjJ]lwmGQ|]g~c]Hl]pl`Pd^?loIcsoNir_kikBYyg?NarXZEGYspt_vLBIoj}LI[uBFvm}tbqvC|xyR~a{kob|HlctZslTGtPDhBKsNsoZPuH`U`Fqg{gKnGSHVLJ^O`zmNgMn~{rsQuoymw^JY?iUBvw_~mMr|GrPHTERS[MiNpY[Mm{ggHpzRaJaoFomtdaQ_?xuTRm}@KjU~RtPsAdxa|uHmy}n^i||FVL[eQAPrWfLm^ndczgF~Nk~aplQvTUpHvnTya]kOenZlLAQIm{lPl@CCTchvCF[fI{^zPkeYZTiamoEcKmBMfZhk_j_~Fjp|wPVZlkh_nHu]@tP|hS@^G^PdsQ~f[RqgTDqezxNFcaO}HZhb|MMiNSYSAnQWCDJukT~e|OTgc}sf[cnr?fyzTa|EwEtRG|I~|IO}O]S|rp]CQ}}DWhSjC_|z|oY|FYl@WkCOoPuWuqr{fJu?Brs^_EBI[@_OCKs}?]O`jnDiXBvaIWhhMAQDNb{U`bqVR}oqVAvR@AZHEBY@depD]OLh`kf^UsHhzKT}CS}HQKy}Q~AeMydXPQztWSSzDnghULQgMAmbWIZ|lWWeEXrE^EeNoZApooEmrXe{NAnoDf`m}UNlRdqQ@jOc~HLOMWs]IDqJHYoMziEedGBPOxOb?[X`KxkFRg@`mgFYnP{hSaxwZfBQqTm}_?RSEaQga]w[vxc]hMne}VfSlqUeMo_iqmd`ilnJXnhdj^EEFifvZyxYFRf^VaqBhLyrGlk~qowqzHOBlOwtx?i{m~`n^G?Yxzxux}b{LSlx]dS~thO^lYE}bzKmUEzwW^{rPGhbEov[Plv??xtyKJshbG`KuO?hjBdS@Ru}iGpvFXJRrvOlrKN?`I_n_tplk}kgwSXuKylXbRQ]]?a|{xiT[li?k]CJpwy^o@ebyGQrPfF`aszGKp]baIx~H?ElETtFh]dz[OjGl@C?]VDhr}OE@V]wLTc[WErXacM{We`F|utKKjgllAxvsVYBZ@HcuMgLboFHVZmi}eIXAIFhS@A@FGRbjeoJWZ_NKd^oEH`qgy`q[Tq{x?LRP|GfBFFJV|fgZs`MLbpPYUdIV^]mD@FG]pYAT^A^RNCcXVrPsgk{jTrAIQPs_`mD}rOqAZA[}RETFz]WkXFTz_m{N@{W@_fPKZLT`@aIqf|L^Mb|crNqZ{BVsijzpGPEKQQZGlApDn`ruH}cvF|iXcNqK}cxe_U~HRnKV}sCYb`D~oGvwG[Ca|UaybXea~DdD~LiIbGRxJ_VGheI{ika}KC[OZJLn^IBkPrQj_EuoFwZ}DpoBRcK]Q}?EmTv~i_Tul{bky?Iit~tgS|o}JL_VYcCQdjeJ_MfaA`FgCgc[Ii|CBHwq~nbJeYTK{e`CNstKfTKPzw{jdhp|qsZyP_FcugxCFNpKitlR~vUrx^NrSVsSTaEgnxZTmKc`R|lGJeX}ccKLsQZQhsFkeFd|ckHIVTlGMg`~uPwuHRJS_CPuN_ogXe{Ba}dO_UBhuNXby|h?JlgBIqMKx^_u{molgL[W_iavNQuOq?ap]PGB`clAicnl@k~pA?MWHEZ{HuTLsCpOxxrKlBh]FyMjLdFl|nMIvTHyGAlPogqfZ?PlvlFJvYnDQd}R@uAhtJmDfe|iJqdkYr}r@mEjjIetDl_I`TELfoR|qTBu@Tic[BaXjP?dCS~MUK[HPRI}OUOwAaf|_}HZzrwXvbnNgltjTwkBE~MztTQhtRSWoQHajMoVyBBA`kdgK~h`o[J`dm~pm]tk@i`[F~F]DBlJKklrkR]SNw@{aG~Vhl`KINsQkOy?WhcqUMTGDOM_]bUjVd|Yh_KUCCgIJ|LDIGZCPls{RzbVWVLEhHvWBzKq|^N?DyJB|__aCUjoEgsARki}j@DQXS`RNU|DJ^a~d{sh_Iu{ONcUtSrGWW@cvUjefHHi}eSSGrNtO?cTPBShLqzwMVjWQQCCFB^culBjZHEK_{dO~Q`YhJYFn]jq~XSnG@[lQr]eKrjXpG~L^h~tDgEma^AUFThlaR{xyuP@[^VFwXSeUbVetufa@dX]CLyAnDV@Bs[DnpeghJw^?UIana}r_CKGDySoRudklbgio}kIDpA@McDoPK?iYcG?_zOmnWfJp}a[JLR[stXMo?_^Ng[whQlrDbrawZeSZ~SJstIObdDSfAA{MV}?gNunLOnbMv_~KFQUAjIMj^GkoGxuYtYbGDImEYiwEMyTpMxN_LSnSMdl{bg@dtAnAMvhDTBR_FxoQgANniRqxd`pWv@rFJ|mWNWmh[GMJz_Nq`BIN@KsjMPASXORcdHjf~rJfgZYe_uulzqM_KdPlMsuvU^YJuLtofPhGonVOQxCMuXliNvJIaoC?hSxcxKVVxWlNs^ENDvCtSmO~WxI[itnjs^RDvI@KqG}YekaSbTaB]ki]XM@[ZnDAP~@|BzLRgOzmjmPkRE@_sobkT|SszXK[rZN?F]Z_u}Yue^[BZgLtR}FHzWyxWEX^wXC]MJmiVbQuBzkgRcKGUhOvUc_bga|Tx`KEM`JWEgTpFYVeXLCm|mctZR@uKTDeUONPozBeIkrY`cz]]~WPGMUf`MNUGHDbxZuO{gmsKYkAGRPqjc|_FtblEOwy}dnwCHo]PJhN~JoteaJ?dmYZeB^Xd?X^pOKDbOMF@Ugg^hETLdhwlA}PL@_ur|o{VZosP?ntJ_kG][g{Zq`Tu]dzQlSWiKfnxDnk}KOzp~tdFstMobmy[oPYjyOtUzMWdjcNSUAjRuqhLS@AwB^{BFnqjCmmlk?jpn}TksS{KcKkDboXiwK]qMVjm~V`LgWhjS^nLGwfhAYrjDSBL_{cRus~{?xar_xqPlArrYFd?pHKdMEZzzjJpfC?Hv}mAuIDkyBxFpxhstTx`IO{rp}XGuQ]VtbHerlRc_LFGWK[XluFcNGUtDYMZny[M^nVKVeMllQI[xtvwQnXFlWYqxZZFp_|]^oWX[{pOMpxXxvkbyJA[DrPzwD|LW|QcV{Nw~U^dgguSpG]ClmO@j_TENIGjPWwgdVbHganhM?ema|dBaqla|WBd`poj~klxaasKxGG^xbWquAl~_lKWxUkDFagMnE{zHug{b`A~IYcQYBF_E}wiA}K@yxWHrZ{[d~|ARsYsjeNWzkMs~IOqqp[yzDE|WFrivsidTcnbHFRoW@XpAV`lv_zj?B~tPCppRjgbbDTALeFaOf?VcjnKTQMLyp{NwdylHCqmo?oelhjWuXj~}{fpuX`fra?GNkDiChYgVSh{R[BgF~eQa^WVz}ATI_CpY?g_diae]|ijH`TyNIF}|D_xpmBq_JpKih{Ba|sWzhnAoyraiDvk`h{qbBfsylBGmRH}DRPdryEsSaKS~tIaeF[s]I~xxHVrcNe@Jjxa@jlhZueLQqHh_]twVMqG_EGuwyab{nxOF?`HCle}nBZzlTQjkLmoXbXhOtBglFoMz?eqre`HiE@vNwBulglmQjj]DB@pPkPUgA^sjOAUNdSu_`oAzar?n?eMnw{{hYmslYi[TnlJD'",...']charCodeAtUinyxpf',"for(;e<10359;c[e++]=p-=128,A=A?p-A&&A:p==34&&p)for(p=1;p<128;y=f.map((n,x)=>(U=r[n]*2+1,U=Math.log(U/(h-U)),t-=a[x]*U,U/500)),t=~-h/(1+Math.exp(t))|1,i=o%h<t,o=o%h+(i?t:h-t)*(o>>17)-!i*t,f.map((n,x)=>(U=r[n]+=(i*h/2-r[n]<<13)/((C[n]+=C[n]<5)+1/20)>>13,a[x]+=y[x]*(i-t/h))),p=p*2+i)for(f='010202103203210431053105410642065206541'.split(t=0).map((n,x)=>(U=0,[...n].map((n,x)=>(U=U*997+(c[e-n]|0)|0)),h*32-1&U*997+p+!!A*129)*12+x);o<h*32;o=o*64|M.charCodeAt(d++)&63);for(C=String.fromCharCode(...c);r=/[\0-#?@\\\\~]/.exec(C);)with(C.split(r))C=join(shift());return C")([],[],1<<17,[0,0,0,0,0,0,0,0,0,0,0,0],new Uint16Array(51e6).fill(1<<15),new Uint8Array(51e6),0,0,0,0));
|
|
3870
|
-
// end of Crypto-JS
|
|
3871
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
3872
|
-
}
|
|
3873
|
-
}
|
|
3975
|
+
// constants for batch rendering
|
|
3976
|
+
gl_VERTICES_PER_QUAD = 6,
|
|
3977
|
+
gl_INDICIES_PER_VERT = 6,
|
|
3978
|
+
gl_MAX_BATCH = 1<<16,
|
|
3979
|
+
gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2, // vec2 * 2 + (char * 4) * 2
|
|
3980
|
+
gl_VERTEX_BUFFER_SIZE = gl_MAX_BATCH * gl_VERTICES_PER_QUAD * gl_VERTEX_BYTE_STRIDE;
|
|
3874
3981
|
/**
|
|
3875
|
-
* LittleJS
|
|
3876
|
-
*
|
|
3877
|
-
*
|
|
3878
|
-
*
|
|
3879
|
-
* -
|
|
3880
|
-
* -
|
|
3881
|
-
* -
|
|
3882
|
-
*
|
|
3982
|
+
* LittleJS - The Tiny JavaScript Game Engine That Can!
|
|
3983
|
+
* MIT License - Copyright 2021 Frank Force
|
|
3984
|
+
*
|
|
3985
|
+
* Engine Features
|
|
3986
|
+
* - Object oriented system with base class engine object
|
|
3987
|
+
* - Base class object handles update, physics, collision, rendering, etc
|
|
3988
|
+
* - Engine helper classes and functions like Vector2, Color, and Timer
|
|
3989
|
+
* - Super fast rendering system for tile sheets
|
|
3990
|
+
* - Sound effects audio with zzfx and music with zzfxm
|
|
3991
|
+
* - Input processing system with gamepad and touchscreen support
|
|
3992
|
+
* - Tile layer rendering and collision system
|
|
3993
|
+
* - Particle effect system
|
|
3994
|
+
* - Medal system tracks and displays achievements
|
|
3995
|
+
* - Debug tools and debug rendering system
|
|
3996
|
+
* - Post processing effects
|
|
3997
|
+
* - Call engineInit() to start it up!
|
|
3998
|
+
* @namespace Engine
|
|
3883
3999
|
*/
|
|
3884
4000
|
|
|
3885
4001
|
'use strict';
|
|
3886
4002
|
|
|
3887
|
-
/**
|
|
3888
|
-
* @type {
|
|
3889
|
-
* @
|
|
3890
|
-
|
|
4003
|
+
/** Name of engine
|
|
4004
|
+
* @type {String}
|
|
4005
|
+
* @default
|
|
4006
|
+
* @memberof Engine */
|
|
4007
|
+
const engineName = 'LittleJS';
|
|
3891
4008
|
|
|
3892
|
-
/**
|
|
3893
|
-
* @type {
|
|
3894
|
-
* @
|
|
3895
|
-
|
|
4009
|
+
/** Version of engine
|
|
4010
|
+
* @type {String}
|
|
4011
|
+
* @default
|
|
4012
|
+
* @memberof Engine */
|
|
4013
|
+
const engineVersion = '1.6.92';
|
|
3896
4014
|
|
|
3897
|
-
/**
|
|
3898
|
-
* @type {
|
|
3899
|
-
* @
|
|
3900
|
-
|
|
4015
|
+
/** Frames per second to update objects
|
|
4016
|
+
* @type {Number}
|
|
4017
|
+
* @default
|
|
4018
|
+
* @memberof Engine */
|
|
4019
|
+
const frameRate = 60;
|
|
3901
4020
|
|
|
3902
|
-
|
|
3903
|
-
|
|
4021
|
+
/** How many seconds each frame lasts, engine uses a fixed time step
|
|
4022
|
+
* @type {Number}
|
|
4023
|
+
* @default 1/60
|
|
4024
|
+
* @memberof Engine */
|
|
4025
|
+
const timeDelta = 1/frameRate;
|
|
3904
4026
|
|
|
3905
|
-
|
|
4027
|
+
/** Array containing all engine objects
|
|
4028
|
+
* @type {Array}
|
|
4029
|
+
* @memberof Engine */
|
|
4030
|
+
let engineObjects = [];
|
|
3906
4031
|
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
glCanvas = document.createElement('canvas');
|
|
3912
|
-
glContext = glCanvas.getContext('webgl', {antialias: false});
|
|
3913
|
-
glTileTexture = glCreateTexture(tileImage);
|
|
4032
|
+
/** Array containing only objects that are set to collide with other objects this frame (for optimization)
|
|
4033
|
+
* @type {Array}
|
|
4034
|
+
* @memberof Engine */
|
|
4035
|
+
let engineObjectsCollide = [];
|
|
3914
4036
|
|
|
3915
|
-
|
|
3916
|
-
|
|
4037
|
+
/** Current update frame, used to calculate time
|
|
4038
|
+
* @type {Number}
|
|
4039
|
+
* @memberof Engine */
|
|
4040
|
+
let frame = 0;
|
|
3917
4041
|
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
'attribute vec2 p,t;'+ // position, uv
|
|
3923
|
-
'attribute vec4 c,a;'+ // color, additiveColor
|
|
3924
|
-
'varying vec4 v,d,e;'+ // return uv, color, additiveColor
|
|
3925
|
-
'void main(){'+ // shader entry point
|
|
3926
|
-
'gl_Position=m*vec4(p,1,1);'+ // transform position
|
|
3927
|
-
'v=vec4(t,p);d=c;e=a;'+ // pass stuff to fragment shader
|
|
3928
|
-
'}' // end of shader
|
|
3929
|
-
,
|
|
3930
|
-
'precision highp float;'+ // use highp for better accuracy
|
|
3931
|
-
'varying vec4 v,d,e;'+ // uv, color, additiveColor
|
|
3932
|
-
'uniform sampler2D s;'+ // texture
|
|
3933
|
-
'void main(){'+ // shader entry point
|
|
3934
|
-
'gl_FragColor=texture2D(s,v.xy)*d+e;'+ // modulate texture by color plus additive
|
|
3935
|
-
'}' // end of shader
|
|
3936
|
-
);
|
|
4042
|
+
/** Current engine time since start in seconds, derived from frame
|
|
4043
|
+
* @type {Number}
|
|
4044
|
+
* @memberof Engine */
|
|
4045
|
+
let time = 0;
|
|
3937
4046
|
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
glColorData = new Uint32Array(vertexData);
|
|
3943
|
-
glBatchCount = 0;
|
|
3944
|
-
}
|
|
4047
|
+
/** Actual clock time since start in seconds (not affected by pause or frame rate clamping)
|
|
4048
|
+
* @type {Number}
|
|
4049
|
+
* @memberof Engine */
|
|
4050
|
+
let timeReal = 0;
|
|
3945
4051
|
|
|
3946
|
-
/**
|
|
3947
|
-
* @
|
|
3948
|
-
* @
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
// setup blending
|
|
3952
|
-
glAdditive = additive;
|
|
3953
|
-
}
|
|
4052
|
+
/** Is the game paused? Causes time and objects to not be updated
|
|
4053
|
+
* @type {Boolean}
|
|
4054
|
+
* @default 0
|
|
4055
|
+
* @memberof Engine */
|
|
4056
|
+
let paused = 0;
|
|
3954
4057
|
|
|
3955
|
-
/** Set
|
|
3956
|
-
*
|
|
3957
|
-
* @
|
|
3958
|
-
|
|
3959
|
-
function glSetTexture(texture=glTileTexture)
|
|
3960
|
-
{
|
|
3961
|
-
// must flush cache with the old texture to set a new one
|
|
3962
|
-
if (texture != glActiveTexture)
|
|
3963
|
-
glFlush();
|
|
4058
|
+
/** Set if game is paused
|
|
4059
|
+
* @param {Boolean} paused
|
|
4060
|
+
* @memberof Engine */
|
|
4061
|
+
function setPaused(_paused) { paused = _paused; }
|
|
3964
4062
|
|
|
3965
|
-
|
|
3966
|
-
}
|
|
4063
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3967
4064
|
|
|
3968
|
-
/**
|
|
3969
|
-
* @param {
|
|
3970
|
-
* @param
|
|
3971
|
-
* @
|
|
3972
|
-
* @
|
|
3973
|
-
|
|
4065
|
+
/** Start up LittleJS engine with your callback functions
|
|
4066
|
+
* @param {Function} gameInit - Called once after the engine starts up, setup the game
|
|
4067
|
+
* @param {Function} gameUpdate - Called every frame at 60 frames per second, handle input and update the game state
|
|
4068
|
+
* @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render
|
|
4069
|
+
* @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
|
|
4070
|
+
* @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
|
|
4071
|
+
* @param {String} [tileImageSource] - Tile image to use, everything starts when the image is finished loading
|
|
4072
|
+
* @memberof Engine */
|
|
4073
|
+
function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, tileImageSource)
|
|
3974
4074
|
{
|
|
3975
|
-
//
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
4075
|
+
// init engine when tiles load or fail to load
|
|
4076
|
+
tileImage.onerror = tileImage.onload = ()=>
|
|
4077
|
+
{
|
|
4078
|
+
// save tile image info
|
|
4079
|
+
tileImageFixBleed = vec2(tileFixBleedScale).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
|
|
4080
|
+
debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
|
|
3979
4081
|
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
4082
|
+
// setup html
|
|
4083
|
+
const styleBody = 'margin:0;overflow:hidden;' + // fill the window
|
|
4084
|
+
'background:#000;' + // set background color
|
|
4085
|
+
'touch-action:none;' + // prevent mobile pinch to resize
|
|
4086
|
+
'user-select:none;' + // prevent mobile hold to select
|
|
4087
|
+
'-webkit-user-select:none'; // compatibility for ios
|
|
4088
|
+
document.body.style = styleBody;
|
|
4089
|
+
document.body.appendChild(mainCanvas = document.createElement('canvas'));
|
|
4090
|
+
mainContext = mainCanvas.getContext('2d');
|
|
3985
4091
|
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
* @return {WebGLProgram}
|
|
3990
|
-
* @memberof WebGL */
|
|
3991
|
-
function glCreateProgram(vsSource, fsSource)
|
|
3992
|
-
{
|
|
3993
|
-
// build the program
|
|
3994
|
-
const program = glContext.createProgram();
|
|
3995
|
-
glContext.attachShader(program, glCompileShader(vsSource, gl_VERTEX_SHADER));
|
|
3996
|
-
glContext.attachShader(program, glCompileShader(fsSource, gl_FRAGMENT_SHADER));
|
|
3997
|
-
glContext.linkProgram(program);
|
|
4092
|
+
// init stuff and start engine
|
|
4093
|
+
debugInit();
|
|
4094
|
+
glEnable && glInit();
|
|
3998
4095
|
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
return program;
|
|
4003
|
-
}
|
|
4096
|
+
// create overlay canvas for hud to appear above gl canvas
|
|
4097
|
+
document.body.appendChild(overlayCanvas = document.createElement('canvas'));
|
|
4098
|
+
overlayContext = overlayCanvas.getContext('2d');
|
|
4004
4099
|
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
{
|
|
4011
|
-
// build the texture
|
|
4012
|
-
const texture = glContext.createTexture();
|
|
4013
|
-
glContext.bindTexture(gl_TEXTURE_2D, texture);
|
|
4014
|
-
image && image.width && glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
|
|
4100
|
+
// set canvas style
|
|
4101
|
+
const styleCanvas = 'position:absolute;' +
|
|
4102
|
+
'top:50%;left:50%;transform:translate(-50%,-50%);' + // center the canvas
|
|
4103
|
+
(canvasPixelated?'image-rendering:pixelated':''); // set pixelated rendering
|
|
4104
|
+
(glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;
|
|
4015
4105
|
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, filter);
|
|
4020
|
-
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE);
|
|
4021
|
-
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE);
|
|
4022
|
-
return texture;
|
|
4023
|
-
}
|
|
4106
|
+
gameInit();
|
|
4107
|
+
engineUpdate();
|
|
4108
|
+
};
|
|
4024
4109
|
|
|
4025
|
-
//
|
|
4026
|
-
|
|
4027
|
-
{
|
|
4028
|
-
// clear and set to same size as main canvas
|
|
4029
|
-
glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
|
|
4030
|
-
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
4110
|
+
// frame time tracking
|
|
4111
|
+
let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS;
|
|
4031
4112
|
|
|
4032
|
-
//
|
|
4033
|
-
|
|
4034
|
-
glContext.activeTexture(gl_TEXTURE0);
|
|
4035
|
-
glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
|
|
4036
|
-
glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
|
|
4037
|
-
glContext.bufferData(gl_ARRAY_BUFFER, gl_VERTEX_BUFFER_SIZE, gl_DYNAMIC_DRAW);
|
|
4038
|
-
glSetBlendMode();
|
|
4039
|
-
|
|
4040
|
-
// set vertex attributes
|
|
4041
|
-
let offset = 0;
|
|
4042
|
-
const initVertexAttribArray = (name, type, typeSize, size, normalize=0)=>
|
|
4113
|
+
// main update loop
|
|
4114
|
+
function engineUpdate(frameTimeMS=0)
|
|
4043
4115
|
{
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4116
|
+
// update time keeping
|
|
4117
|
+
let frameTimeDeltaMS = frameTimeMS - frameTimeLastMS;
|
|
4118
|
+
frameTimeLastMS = frameTimeMS;
|
|
4119
|
+
if (debug || showWatermark)
|
|
4120
|
+
averageFPS = lerp(.05, averageFPS, 1e3/(frameTimeDeltaMS||1));
|
|
4121
|
+
const debugSpeedUp = debug && keyIsDown(107); // +
|
|
4122
|
+
const debugSpeedDown = debug && keyIsDown(109); // -
|
|
4123
|
+
if (debug) // +/- to speed/slow time
|
|
4124
|
+
frameTimeDeltaMS *= debugSpeedUp ? 5 : debugSpeedDown ? .2 : 1;
|
|
4125
|
+
timeReal += frameTimeDeltaMS / 1e3;
|
|
4126
|
+
frameTimeBufferMS += !paused * frameTimeDeltaMS;
|
|
4127
|
+
if (!debugSpeedUp)
|
|
4128
|
+
frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp incase of slow framerate
|
|
4129
|
+
|
|
4130
|
+
if (canvasFixedSize.x)
|
|
4131
|
+
{
|
|
4132
|
+
// clear canvas and set fixed size
|
|
4133
|
+
mainCanvas.width = canvasFixedSize.x;
|
|
4134
|
+
mainCanvas.height = canvasFixedSize.y;
|
|
4135
|
+
|
|
4136
|
+
// fit to window by adding space on top or bottom if necessary
|
|
4137
|
+
const aspect = innerWidth / innerHeight;
|
|
4138
|
+
const fixedAspect = mainCanvas.width / mainCanvas.height;
|
|
4139
|
+
(glCanvas||mainCanvas).style.width = mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
|
|
4140
|
+
(glCanvas||mainCanvas).style.height = mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
|
|
4141
|
+
}
|
|
4142
|
+
else
|
|
4143
|
+
{
|
|
4144
|
+
// clear canvas and set size to same as window
|
|
4145
|
+
mainCanvas.width = min(innerWidth, canvasMaxSize.x);
|
|
4146
|
+
mainCanvas.height = min(innerHeight, canvasMaxSize.y);
|
|
4147
|
+
}
|
|
4148
|
+
|
|
4149
|
+
// clear overlay canvas and set size
|
|
4150
|
+
overlayCanvas.width = mainCanvas.width;
|
|
4151
|
+
overlayCanvas.height = mainCanvas.height;
|
|
4152
|
+
|
|
4153
|
+
// save canvas size
|
|
4154
|
+
mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
|
|
4155
|
+
|
|
4156
|
+
if (paused)
|
|
4157
|
+
{
|
|
4158
|
+
// do post update even when paused
|
|
4159
|
+
inputUpdate();
|
|
4160
|
+
debugUpdate();
|
|
4161
|
+
gameUpdatePost();
|
|
4162
|
+
inputUpdatePost();
|
|
4163
|
+
}
|
|
4164
|
+
else
|
|
4165
|
+
{
|
|
4166
|
+
// apply time delta smoothing, improves smoothness of framerate in some browsers
|
|
4167
|
+
let deltaSmooth = 0;
|
|
4168
|
+
if (frameTimeBufferMS < 0 && frameTimeBufferMS > -9)
|
|
4169
|
+
{
|
|
4170
|
+
// force an update each frame if time is close enough (not just a fast refresh rate)
|
|
4171
|
+
deltaSmooth = frameTimeBufferMS;
|
|
4172
|
+
frameTimeBufferMS = 0;
|
|
4173
|
+
}
|
|
4174
|
+
|
|
4175
|
+
// update multiple frames if necessary in case of slow framerate
|
|
4176
|
+
for (;frameTimeBufferMS >= 0; frameTimeBufferMS -= 1e3 / frameRate)
|
|
4177
|
+
{
|
|
4178
|
+
// update game and objects
|
|
4179
|
+
inputUpdate();
|
|
4180
|
+
gameUpdate();
|
|
4181
|
+
engineObjectsUpdate();
|
|
4182
|
+
|
|
4183
|
+
// do post update
|
|
4184
|
+
debugUpdate();
|
|
4185
|
+
gameUpdatePost();
|
|
4186
|
+
inputUpdatePost();
|
|
4187
|
+
}
|
|
4188
|
+
|
|
4189
|
+
// add the time smoothing back in
|
|
4190
|
+
frameTimeBufferMS += deltaSmooth;
|
|
4191
|
+
}
|
|
4192
|
+
|
|
4193
|
+
// render sort then render while removing destroyed objects
|
|
4194
|
+
enginePreRender();
|
|
4195
|
+
gameRender();
|
|
4196
|
+
engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
|
|
4197
|
+
for (const o of engineObjects)
|
|
4198
|
+
o.destroyed || o.render();
|
|
4199
|
+
gameRenderPost();
|
|
4200
|
+
glRenderPostProcess();
|
|
4201
|
+
medalsRender();
|
|
4202
|
+
touchGamepadRender();
|
|
4203
|
+
debugRender();
|
|
4204
|
+
glEnable && glCopyToContext(mainContext);
|
|
4205
|
+
|
|
4206
|
+
if (showWatermark)
|
|
4207
|
+
{
|
|
4208
|
+
// update fps
|
|
4209
|
+
overlayContext.textAlign = 'right';
|
|
4210
|
+
overlayContext.textBaseline = 'top';
|
|
4211
|
+
overlayContext.font = '1em monospace';
|
|
4212
|
+
overlayContext.fillStyle = '#000';
|
|
4213
|
+
const text = engineName + ' ' + 'v' + engineVersion + ' / '
|
|
4214
|
+
+ drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
|
|
4215
|
+
+ (glEnable ? ' GL' : ' 2D') ;
|
|
4216
|
+
overlayContext.fillText(text, mainCanvas.width-3, 3);
|
|
4217
|
+
overlayContext.fillStyle = '#fff';
|
|
4218
|
+
overlayContext.fillText(text, mainCanvas.width-2, 2);
|
|
4219
|
+
drawCount = 0;
|
|
4220
|
+
}
|
|
4221
|
+
|
|
4222
|
+
requestAnimationFrame(engineUpdate);
|
|
4048
4223
|
}
|
|
4049
|
-
initVertexAttribArray('p', gl_FLOAT, 4, 2); // position
|
|
4050
|
-
initVertexAttribArray('t', gl_FLOAT, 4, 2); // texture coords
|
|
4051
|
-
initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
|
|
4052
|
-
initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
|
|
4053
4224
|
|
|
4054
|
-
//
|
|
4055
|
-
|
|
4056
|
-
const sy = 2 * cameraScale / mainCanvas.height;
|
|
4057
|
-
glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), 0,
|
|
4058
|
-
new Float32Array([
|
|
4059
|
-
sx, 0, 0, 0,
|
|
4060
|
-
0, sy, 0, 0,
|
|
4061
|
-
1, 1, -1, 1,
|
|
4062
|
-
-1-sx*cameraPos.x, -1-sy*cameraPos.y, 0, 0
|
|
4063
|
-
])
|
|
4064
|
-
);
|
|
4225
|
+
// set tile image source to load the image and start the engine
|
|
4226
|
+
tileImageSource ? tileImage.src = tileImageSource : tileImage.onload();
|
|
4065
4227
|
}
|
|
4066
4228
|
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
function glFlush()
|
|
4229
|
+
// Called automatically by engine to setup render system
|
|
4230
|
+
function enginePreRender()
|
|
4070
4231
|
{
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
const destBlend = glBatchAdditive ? gl_ONE : gl_ONE_MINUS_SRC_ALPHA;
|
|
4074
|
-
glContext.blendFuncSeparate(gl_SRC_ALPHA, destBlend, gl_ONE, destBlend);
|
|
4075
|
-
glContext.enable(gl_BLEND);
|
|
4232
|
+
// save canvas size
|
|
4233
|
+
mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
|
|
4076
4234
|
|
|
4077
|
-
//
|
|
4078
|
-
|
|
4079
|
-
glPositionData.subarray(0, glBatchCount * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT));
|
|
4080
|
-
glContext.drawArrays(gl_TRIANGLES, 0, glBatchCount * gl_VERTICES_PER_QUAD);
|
|
4081
|
-
glBatchCount = 0;
|
|
4082
|
-
glBatchAdditive = glAdditive;
|
|
4083
|
-
}
|
|
4235
|
+
// disable smoothing for pixel art
|
|
4236
|
+
mainContext.imageSmoothingEnabled = !canvasPixelated;
|
|
4084
4237
|
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
* @param {Boolean} [forceDraw=0]
|
|
4088
|
-
* @memberof WebGL */
|
|
4089
|
-
function glCopyToContext(context, forceDraw)
|
|
4090
|
-
{
|
|
4091
|
-
if (!glBatchCount && !forceDraw) return;
|
|
4092
|
-
|
|
4093
|
-
glFlush();
|
|
4094
|
-
|
|
4095
|
-
// do not draw in overlay mode because the canvas is visible
|
|
4096
|
-
if (!glOverlay || forceDraw)
|
|
4097
|
-
context.drawImage(glCanvas, 0, 0);
|
|
4238
|
+
// setup gl rendering if enabled
|
|
4239
|
+
glEnable && glPreRender();
|
|
4098
4240
|
}
|
|
4099
4241
|
|
|
4100
|
-
/**
|
|
4101
|
-
* @
|
|
4102
|
-
|
|
4103
|
-
* @param sizeX
|
|
4104
|
-
* @param sizeY
|
|
4105
|
-
* @param angle
|
|
4106
|
-
* @param uv0X
|
|
4107
|
-
* @param uv0Y
|
|
4108
|
-
* @param uv1X
|
|
4109
|
-
* @param uv1Y
|
|
4110
|
-
* @param rgba
|
|
4111
|
-
* @param [rgbaAdditive=0]
|
|
4112
|
-
* @memberof WebGL */
|
|
4113
|
-
function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdditive=0)
|
|
4242
|
+
/** Update each engine object, remove destroyed objects, and update time
|
|
4243
|
+
* @memberof Engine */
|
|
4244
|
+
function engineObjectsUpdate()
|
|
4114
4245
|
{
|
|
4115
|
-
//
|
|
4116
|
-
|
|
4117
|
-
glFlush();
|
|
4246
|
+
// get list of solid objects for physics optimzation
|
|
4247
|
+
engineObjectsCollide = engineObjects.filter(o=>o.collideSolidObjects);
|
|
4118
4248
|
|
|
4119
|
-
//
|
|
4120
|
-
|
|
4121
|
-
const cx = c*sizeX, cy = c*sizeY, sx = s*sizeX, sy = s*sizeY;
|
|
4122
|
-
|
|
4123
|
-
// setup 2 triangles to form a quad
|
|
4124
|
-
for(let i=6, offset = glBatchCount++ * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT; i--;)
|
|
4249
|
+
// recursive object update
|
|
4250
|
+
function updateObject(o)
|
|
4125
4251
|
{
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
glColorData[offset++] = rgbaAdditive;
|
|
4252
|
+
if (!o.destroyed)
|
|
4253
|
+
{
|
|
4254
|
+
o.update();
|
|
4255
|
+
for (const child of o.children)
|
|
4256
|
+
updateObject(child);
|
|
4257
|
+
}
|
|
4133
4258
|
}
|
|
4134
|
-
|
|
4259
|
+
for (const o of engineObjects)
|
|
4260
|
+
o.parent || updateObject(o);
|
|
4135
4261
|
|
|
4136
|
-
|
|
4137
|
-
|
|
4262
|
+
// remove destroyed objects
|
|
4263
|
+
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
4138
4264
|
|
|
4139
|
-
|
|
4265
|
+
// increment frame and update time
|
|
4266
|
+
time = ++frame / frameRate;
|
|
4267
|
+
}
|
|
4140
4268
|
|
|
4141
|
-
/**
|
|
4142
|
-
* @
|
|
4143
|
-
|
|
4144
|
-
* @memberof WebGL */
|
|
4145
|
-
function glInitPostProcess(shaderCode, includeOverlay)
|
|
4269
|
+
/** Destroy and remove all objects
|
|
4270
|
+
* @memberof Engine */
|
|
4271
|
+
function engineObjectsDestroy()
|
|
4146
4272
|
{
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture2D(iChannel0,p/iResolution.xy);}';
|
|
4151
|
-
|
|
4152
|
-
// create the shader
|
|
4153
|
-
glPostShader = glCreateProgram(
|
|
4154
|
-
'precision highp float;'+ // use highp for better accuracy
|
|
4155
|
-
'attribute vec2 p;'+ // position
|
|
4156
|
-
'void main(){'+ // shader entry point
|
|
4157
|
-
'gl_Position=vec4(p,1,1);'+ // set position
|
|
4158
|
-
'}' // end of shader
|
|
4159
|
-
,
|
|
4160
|
-
'precision highp float;'+ // use highp for better accuracy
|
|
4161
|
-
'uniform sampler2D iChannel0;'+ // input texture
|
|
4162
|
-
'uniform vec3 iResolution;'+ // size of output texture
|
|
4163
|
-
'uniform float iTime;'+ // time passed
|
|
4164
|
-
'\n' + shaderCode + '\n'+ // insert custom shader code
|
|
4165
|
-
'void main(){'+ // shader entry point
|
|
4166
|
-
'mainImage(gl_FragColor,gl_FragCoord.xy);'+ // call post process function
|
|
4167
|
-
'gl_FragColor.a=1.;'+ // always use full alpha
|
|
4168
|
-
'}' // end of shader
|
|
4169
|
-
);
|
|
4170
|
-
|
|
4171
|
-
// create buffer and texture
|
|
4172
|
-
glPostArrayBuffer = glContext.createBuffer();
|
|
4173
|
-
glPostTexture = glCreateTexture();
|
|
4174
|
-
glPostIncludeOverlay = includeOverlay;
|
|
4175
|
-
|
|
4176
|
-
// hide the original 2d canvas
|
|
4177
|
-
mainCanvas.style.visibility = 'hidden';
|
|
4273
|
+
for (const o of engineObjects)
|
|
4274
|
+
o.parent || o.destroy();
|
|
4275
|
+
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
4178
4276
|
}
|
|
4179
4277
|
|
|
4180
|
-
|
|
4181
|
-
|
|
4278
|
+
/** Triggers a callback for each object within a given area
|
|
4279
|
+
* @param {Vector2} [pos] - Center of test area
|
|
4280
|
+
* @param {Number} [size] - Radius of circle if float, rectangle size if Vector2
|
|
4281
|
+
* @param {Function} [callbackFunction] - Calls this function on every object that passes the test
|
|
4282
|
+
* @param {Array} [objects=engineObjects] - List of objects to check
|
|
4283
|
+
* @memberof Engine */
|
|
4284
|
+
function engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
|
|
4182
4285
|
{
|
|
4183
|
-
if (!
|
|
4184
|
-
return;
|
|
4185
|
-
|
|
4186
|
-
// prepare to render post process shader
|
|
4187
|
-
if (glEnable)
|
|
4286
|
+
if (!pos) // all objects
|
|
4188
4287
|
{
|
|
4189
|
-
|
|
4190
|
-
|
|
4288
|
+
for (const o of objects)
|
|
4289
|
+
callbackFunction(o);
|
|
4191
4290
|
}
|
|
4192
|
-
else //
|
|
4193
|
-
glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
|
|
4194
|
-
|
|
4195
|
-
if (glPostIncludeOverlay)
|
|
4291
|
+
else if (size.x != undefined) // bounding box test
|
|
4196
4292
|
{
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
// clear overlay canvas
|
|
4201
|
-
overlayCanvas.width = mainCanvas.width;
|
|
4293
|
+
for (const o of objects)
|
|
4294
|
+
isOverlapping(pos, size, o.pos, o.size) && callbackFunction(o);
|
|
4202
4295
|
}
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
// set textures, pass in the 2d canvas and gl canvas in separate texture channels
|
|
4212
|
-
glContext.activeTexture(gl_TEXTURE0);
|
|
4213
|
-
glContext.bindTexture(gl_TEXTURE_2D, glPostTexture);
|
|
4214
|
-
glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, mainCanvas);
|
|
4215
|
-
|
|
4216
|
-
// set vertex position attribute
|
|
4217
|
-
const vertexByteStride = 8;
|
|
4218
|
-
const pLocation = glContext.getAttribLocation(glPostShader, 'p');
|
|
4219
|
-
glContext.enableVertexAttribArray(pLocation);
|
|
4220
|
-
glContext.vertexAttribPointer(pLocation, 2, gl_FLOAT, 0, vertexByteStride, 0);
|
|
4221
|
-
|
|
4222
|
-
// set uniforms and draw
|
|
4223
|
-
const uniformLocation = (name)=>glContext.getUniformLocation(glPostShader, name);
|
|
4224
|
-
glContext.uniform1i(uniformLocation('iChannel0'), 0);
|
|
4225
|
-
glContext.uniform1f(uniformLocation('iTime'), time);
|
|
4226
|
-
glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
|
|
4227
|
-
glContext.drawArrays(gl_TRIANGLES, 0, 3);
|
|
4228
|
-
}
|
|
4229
|
-
|
|
4230
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
4231
|
-
// store gl constants as integers so their name doesn't use space in minifed
|
|
4232
|
-
const
|
|
4233
|
-
gl_ONE = 1,
|
|
4234
|
-
gl_TRIANGLES = 4,
|
|
4235
|
-
gl_SRC_ALPHA = 770,
|
|
4236
|
-
gl_ONE_MINUS_SRC_ALPHA = 771,
|
|
4237
|
-
gl_BLEND = 3042,
|
|
4238
|
-
gl_TEXTURE_2D = 3553,
|
|
4239
|
-
gl_UNSIGNED_BYTE = 5121,
|
|
4240
|
-
gl_BYTE = 5120,
|
|
4241
|
-
gl_FLOAT = 5126,
|
|
4242
|
-
gl_RGBA = 6408,
|
|
4243
|
-
gl_NEAREST = 9728,
|
|
4244
|
-
gl_LINEAR = 9729,
|
|
4245
|
-
gl_TEXTURE_MAG_FILTER = 10240,
|
|
4246
|
-
gl_TEXTURE_MIN_FILTER = 10241,
|
|
4247
|
-
gl_TEXTURE_WRAP_S = 10242,
|
|
4248
|
-
gl_TEXTURE_WRAP_T = 10243,
|
|
4249
|
-
gl_COLOR_BUFFER_BIT = 16384,
|
|
4250
|
-
gl_CLAMP_TO_EDGE = 33071,
|
|
4251
|
-
gl_TEXTURE0 = 33984,
|
|
4252
|
-
gl_TEXTURE1 = 33985,
|
|
4253
|
-
gl_ARRAY_BUFFER = 34962,
|
|
4254
|
-
gl_STATIC_DRAW = 35044,
|
|
4255
|
-
gl_DYNAMIC_DRAW = 35048,
|
|
4256
|
-
gl_FRAGMENT_SHADER = 35632,
|
|
4257
|
-
gl_VERTEX_SHADER = 35633,
|
|
4258
|
-
gl_COMPILE_STATUS = 35713,
|
|
4259
|
-
gl_LINK_STATUS = 35714,
|
|
4260
|
-
gl_UNPACK_FLIP_Y_WEBGL = 37440,
|
|
4261
|
-
|
|
4262
|
-
// constants for batch rendering
|
|
4263
|
-
gl_VERTICES_PER_QUAD = 6,
|
|
4264
|
-
gl_INDICIES_PER_VERT = 6,
|
|
4265
|
-
gl_MAX_BATCH = 1<<16,
|
|
4266
|
-
gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2, // vec2 * 2 + (char * 4) * 2
|
|
4267
|
-
gl_VERTEX_BUFFER_SIZE = gl_MAX_BATCH * gl_VERTICES_PER_QUAD * gl_VERTEX_BYTE_STRIDE;
|
|
4296
|
+
else // circle test
|
|
4297
|
+
{
|
|
4298
|
+
const sizeSquared = size*size;
|
|
4299
|
+
for (const o of objects)
|
|
4300
|
+
pos.distanceSquared(o.pos) < sizeSquared && callbackFunction(o);
|
|
4301
|
+
}
|
|
4302
|
+
}
|