littlejsengine 1.4.7 → 1.4.8
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 +6 -1
- package/build.bat +7 -3
- package/engine/engine.all.js +266 -295
- package/engine/engine.all.min.js +1 -1
- package/engine/engine.all.module.js +276 -315
- package/engine/engine.all.module.min.js +1 -0
- package/engine/engine.all.release.js +266 -295
- package/engine/engine.js +21 -30
- package/engine/engineAudio.js +8 -15
- package/engine/engineBuild.bat +10 -3
- package/engine/engineDraw.js +14 -18
- package/engine/engineExport.js +10 -20
- package/engine/engineInput.js +84 -71
- package/engine/engineMedals.js +25 -32
- package/engine/engineObject.js +2 -2
- package/engine/engineParticles.js +7 -6
- package/engine/engineSettings.js +27 -27
- package/engine/engineTileLayer.js +15 -16
- package/engine/engineUtilities.js +26 -13
- package/engine/engineWebGL.js +38 -66
- package/engine/index.d.ts +52 -39
- package/examples/breakout/index.html +3 -3
- package/examples/module/index.html +1 -1
- package/examples/particles/index.html +12 -12
- package/examples/platformer/index.html +6 -6
- package/examples/puzzle/index.html +2 -2
- package/examples/stress/index.html +1 -1
- package/favicon.png +0 -0
- package/game.js +6 -8
- package/index.html +13 -13
- package/package.json +1 -1
- package/engine/engine.all.release.module.js +0 -4464
|
@@ -137,7 +137,7 @@ const wave = (frequency=1, amplitude=1, t=time)=> amplitude/2 * (1 - Math.cos(t*
|
|
|
137
137
|
* @param {Number} t - time in seconds
|
|
138
138
|
* @return {String}
|
|
139
139
|
* @memberof Utilities */
|
|
140
|
-
const formatTime = (t)=> (t/60|0)+':'+(t%60<10?'0':'')+(t%60|0);
|
|
140
|
+
const formatTime = (t)=> (t/60|0) + ':' + (t%60<10?'0':'') + (t%60|0);
|
|
141
141
|
|
|
142
142
|
///////////////////////////////////////////////////////////////////////////////
|
|
143
143
|
|
|
@@ -161,7 +161,7 @@ const randInt = (a=1, b=0)=> rand(a,b)|0;
|
|
|
161
161
|
/** Randomly returns either -1 or 1
|
|
162
162
|
* @return {Number}
|
|
163
163
|
* @memberof Random */
|
|
164
|
-
const randSign = ()=> (
|
|
164
|
+
const randSign = ()=> randInt(2) * 2 - 1;
|
|
165
165
|
|
|
166
166
|
/** Returns a random Vector2 within a circular shape
|
|
167
167
|
* @param {Number} [radius=1]
|
|
@@ -185,10 +185,15 @@ const randVector = (length=1)=> new Vector2().setAngle(rand(2*PI), length);
|
|
|
185
185
|
const randColor = (cA = new Color, cB = new Color(0,0,0,1), linear)=>
|
|
186
186
|
linear ? cA.lerp(cB, rand()) : new Color(rand(cA.r,cB.r),rand(cA.g,cB.g),rand(cA.b,cB.b),rand(cA.a,cB.a));
|
|
187
187
|
|
|
188
|
-
/**
|
|
188
|
+
/** Seed used by the randSeeded function
|
|
189
189
|
* @memberof Random */
|
|
190
190
|
let randSeed = 1;
|
|
191
191
|
|
|
192
|
+
/** Set seed used by the randSeeded function, should not be 0
|
|
193
|
+
* @param {Number} seed
|
|
194
|
+
* @memberof Random */
|
|
195
|
+
const setRandSeed = (seed)=> randSeed = seed;
|
|
196
|
+
|
|
192
197
|
/** Returns a seeded random value between the two values passed in using randSeed
|
|
193
198
|
* @param {Number} [valueA=1]
|
|
194
199
|
* @param {Number} [valueB=0]
|
|
@@ -216,6 +221,14 @@ const randSeeded = (a=1, b=0)=>
|
|
|
216
221
|
*/
|
|
217
222
|
const vec2 = (x=0, y)=> x.x == undefined ? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y);
|
|
218
223
|
|
|
224
|
+
/**
|
|
225
|
+
* Check if object is a valid Vector2
|
|
226
|
+
* @param {Vector2} vector
|
|
227
|
+
* @return {Boolean}
|
|
228
|
+
* @memberof Utilities
|
|
229
|
+
*/
|
|
230
|
+
const isVector2 = (v)=> !isNaN(v.x) && !isNaN(v.y);
|
|
231
|
+
|
|
219
232
|
/**
|
|
220
233
|
* 2D Vector object with vector math library
|
|
221
234
|
* <br> - Functions do not change this so they can be chained together
|
|
@@ -245,27 +258,27 @@ class Vector2
|
|
|
245
258
|
/** Returns a copy of this vector plus the vector passed in
|
|
246
259
|
* @param {Vector2} vector
|
|
247
260
|
* @return {Vector2} */
|
|
248
|
-
add(v) { ASSERT(v
|
|
261
|
+
add(v) { ASSERT(isVector2(v)); return new Vector2(this.x + v.x, this.y + v.y); }
|
|
249
262
|
|
|
250
263
|
/** Returns a copy of this vector minus the vector passed in
|
|
251
264
|
* @param {Vector2} vector
|
|
252
265
|
* @return {Vector2} */
|
|
253
|
-
subtract(v) { ASSERT(v
|
|
266
|
+
subtract(v) { ASSERT(isVector2(v)); return new Vector2(this.x - v.x, this.y - v.y); }
|
|
254
267
|
|
|
255
268
|
/** Returns a copy of this vector times the vector passed in
|
|
256
269
|
* @param {Vector2} vector
|
|
257
270
|
* @return {Vector2} */
|
|
258
|
-
multiply(v) { ASSERT(v
|
|
271
|
+
multiply(v) { ASSERT(isVector2(v)); return new Vector2(this.x * v.x, this.y * v.y); }
|
|
259
272
|
|
|
260
273
|
/** Returns a copy of this vector divided by the vector passed in
|
|
261
274
|
* @param {Vector2} vector
|
|
262
275
|
* @return {Vector2} */
|
|
263
|
-
divide(v) { ASSERT(v
|
|
276
|
+
divide(v) { ASSERT(isVector2(v)); return new Vector2(this.x / v.x, this.y / v.y); }
|
|
264
277
|
|
|
265
278
|
/** Returns a copy of this vector scaled by the vector passed in
|
|
266
279
|
* @param {Number} scale
|
|
267
280
|
* @return {Vector2} */
|
|
268
|
-
scale(s) { ASSERT(s
|
|
281
|
+
scale(s) { ASSERT(!isVector2(s)); return new Vector2(this.x * s, this.y * s); }
|
|
269
282
|
|
|
270
283
|
/** Returns the length of this vector
|
|
271
284
|
* @return {Number} */
|
|
@@ -298,12 +311,12 @@ class Vector2
|
|
|
298
311
|
/** Returns the dot product of this and the vector passed in
|
|
299
312
|
* @param {Vector2} vector
|
|
300
313
|
* @return {Number} */
|
|
301
|
-
dot(v) { ASSERT(v
|
|
314
|
+
dot(v) { ASSERT(isVector2(v)); return this.x*v.x + this.y*v.y; }
|
|
302
315
|
|
|
303
316
|
/** Returns the cross product of this and the vector passed in
|
|
304
317
|
* @param {Vector2} vector
|
|
305
318
|
* @return {Number} */
|
|
306
|
-
cross(v) { ASSERT(v
|
|
319
|
+
cross(v) { ASSERT(isVector2(v)); return this.x*v.y - this.y*v.x; }
|
|
307
320
|
|
|
308
321
|
/** Returns the angle of this vector, up is angle 0
|
|
309
322
|
* @return {Number} */
|
|
@@ -339,7 +352,7 @@ class Vector2
|
|
|
339
352
|
* @param {Vector2} vector
|
|
340
353
|
* @param {Number} percent
|
|
341
354
|
* @return {Vector2} */
|
|
342
|
-
lerp(v, p) { ASSERT(v
|
|
355
|
+
lerp(v, p) { ASSERT(isVector2(v)); return this.add(v.subtract(this).scale(clamp(p))); }
|
|
343
356
|
|
|
344
357
|
/** Returns true if this vector is within the bounds of an array size passed in
|
|
345
358
|
* @param {Vector2} arraySize
|
|
@@ -350,7 +363,7 @@ class Vector2
|
|
|
350
363
|
* @param {float} digits - precision to display
|
|
351
364
|
* @return {String} */
|
|
352
365
|
toString(digits=3)
|
|
353
|
-
{ return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`; }
|
|
366
|
+
{ if (debug) { return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`; }}
|
|
354
367
|
}
|
|
355
368
|
|
|
356
369
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -588,7 +601,7 @@ class Timer
|
|
|
588
601
|
|
|
589
602
|
/** Returns this timer expressed as a string
|
|
590
603
|
* @return {String} */
|
|
591
|
-
toString() { if (debug) { return this.unset() ? 'unset' : Math.abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ); }
|
|
604
|
+
toString() { if (debug) { return this.unset() ? 'unset' : Math.abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ); }}
|
|
592
605
|
|
|
593
606
|
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
594
607
|
* @return {Number} */
|
|
@@ -601,6 +614,20 @@ class Timer
|
|
|
601
614
|
|
|
602
615
|
'use strict';
|
|
603
616
|
|
|
617
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
618
|
+
// Camera settings
|
|
619
|
+
|
|
620
|
+
/** Position of camera in world space
|
|
621
|
+
* @type {Vector2}
|
|
622
|
+
* @default
|
|
623
|
+
* @memberof Settings */
|
|
624
|
+
let cameraPos = vec2();
|
|
625
|
+
|
|
626
|
+
/** Scale of camera in world space
|
|
627
|
+
* @default
|
|
628
|
+
* @memberof Settings */
|
|
629
|
+
let cameraScale = 16;
|
|
630
|
+
|
|
604
631
|
///////////////////////////////////////////////////////////////////////////////
|
|
605
632
|
// Display settings
|
|
606
633
|
|
|
@@ -627,6 +654,19 @@ let cavasPixelated = 1;
|
|
|
627
654
|
* @memberof Settings */
|
|
628
655
|
let fontDefault = 'arial';
|
|
629
656
|
|
|
657
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
658
|
+
// WebGL settings
|
|
659
|
+
|
|
660
|
+
/** Enable webgl rendering, webgl can be disabled and removed from build (with some features disabled)
|
|
661
|
+
* @default
|
|
662
|
+
* @memberof Settings */
|
|
663
|
+
let glEnable = 1;
|
|
664
|
+
|
|
665
|
+
/** Fixes slow rendering in some browsers by not compositing the WebGL canvas
|
|
666
|
+
* @default
|
|
667
|
+
* @memberof Settings */
|
|
668
|
+
let glOverlay = 1;
|
|
669
|
+
|
|
630
670
|
///////////////////////////////////////////////////////////////////////////////
|
|
631
671
|
// Tile sheet settings
|
|
632
672
|
|
|
@@ -695,33 +735,6 @@ let gravity = 0;
|
|
|
695
735
|
* @memberof Settings */
|
|
696
736
|
let particleEmitRateScale = 1;
|
|
697
737
|
|
|
698
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
699
|
-
// Camera settings
|
|
700
|
-
|
|
701
|
-
/** Position of camera in world space
|
|
702
|
-
* @type {Vector2}
|
|
703
|
-
* @default
|
|
704
|
-
* @memberof Settings */
|
|
705
|
-
let cameraPos = vec2();
|
|
706
|
-
|
|
707
|
-
/** Scale of camera in world space
|
|
708
|
-
* @default
|
|
709
|
-
* @memberof Settings */
|
|
710
|
-
let cameraScale = max(tileSizeDefault.x, tileSizeDefault.y);
|
|
711
|
-
|
|
712
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
713
|
-
// WebGL settings
|
|
714
|
-
|
|
715
|
-
/** Enable webgl rendering, webgl can be disabled and removed from build (with some features disabled)
|
|
716
|
-
* @default
|
|
717
|
-
* @memberof Settings */
|
|
718
|
-
let glEnable = 1;
|
|
719
|
-
|
|
720
|
-
/** Fixes slow rendering in some browsers by not compositing the WebGL canvas
|
|
721
|
-
* @default
|
|
722
|
-
* @memberof Settings */
|
|
723
|
-
let glOverlay = 1;
|
|
724
|
-
|
|
725
738
|
///////////////////////////////////////////////////////////////////////////////
|
|
726
739
|
// Input settings
|
|
727
740
|
|
|
@@ -832,6 +845,7 @@ let medalDisplayIconSize = 50;
|
|
|
832
845
|
- Particle effect system
|
|
833
846
|
- Medal system tracks and displays achievements
|
|
834
847
|
- Debug tools and debug rendering system
|
|
848
|
+
- Post processing effects
|
|
835
849
|
- Call engineInit() to start it up!
|
|
836
850
|
*/
|
|
837
851
|
|
|
@@ -841,7 +855,7 @@ let medalDisplayIconSize = 50;
|
|
|
841
855
|
const engineName = 'LittleJS';
|
|
842
856
|
|
|
843
857
|
/** Version of engine */
|
|
844
|
-
const engineVersion = '1.4.
|
|
858
|
+
const engineVersion = '1.4.8';
|
|
845
859
|
|
|
846
860
|
/** Frames per second to update objects
|
|
847
861
|
* @default */
|
|
@@ -866,14 +880,13 @@ let time = 0;
|
|
|
866
880
|
/** Actual clock time since start in seconds (not affected by pause or frame rate clamping) */
|
|
867
881
|
let timeReal = 0;
|
|
868
882
|
|
|
869
|
-
/** Is the game paused? Causes time and objects to not be updated
|
|
883
|
+
/** Is the game paused? Causes time and objects to not be updated */
|
|
870
884
|
let paused = 0;
|
|
871
885
|
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
let averageFPS, drawCount;
|
|
886
|
+
/** Set if game is paused
|
|
887
|
+
* @param {Boolean} paused
|
|
888
|
+
*/
|
|
889
|
+
function setPaused(_paused) { paused = _paused; }
|
|
877
890
|
|
|
878
891
|
///////////////////////////////////////////////////////////////////////////////
|
|
879
892
|
|
|
@@ -894,13 +907,11 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
894
907
|
tileImageFixBleed = vec2(tileFixBleedScale).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
|
|
895
908
|
debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
|
|
896
909
|
|
|
897
|
-
// setup
|
|
910
|
+
// setup html
|
|
898
911
|
const styleBody = 'margin:0;overflow:hidden;background:#000' + // fill the window
|
|
899
912
|
';touch-action:none' + // prevent mobile pinch to resize
|
|
900
913
|
';user-select:none' + // prevent mobile hold to select
|
|
901
|
-
';-webkit-user-select:none
|
|
902
|
-
|
|
903
|
-
// setup html
|
|
914
|
+
';-webkit-user-select:none'; // compatibility for ios
|
|
904
915
|
document.body.style = styleBody;
|
|
905
916
|
document.body.appendChild(mainCanvas = document.createElement('canvas'));
|
|
906
917
|
mainContext = mainCanvas.getContext('2d');
|
|
@@ -915,7 +926,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
915
926
|
|
|
916
927
|
// set canvas style to fill the window
|
|
917
928
|
const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
|
|
918
|
-
(glCanvas||mainCanvas).style =
|
|
929
|
+
(glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;
|
|
919
930
|
|
|
920
931
|
gameInit();
|
|
921
932
|
touchGamepadCreate();
|
|
@@ -923,16 +934,16 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
923
934
|
};
|
|
924
935
|
|
|
925
936
|
// frame time tracking
|
|
926
|
-
let frameTimeLastMS = 0, frameTimeBufferMS
|
|
937
|
+
let frameTimeLastMS = 0, frameTimeBufferMS, averageFPS;
|
|
927
938
|
|
|
928
939
|
// main update loop
|
|
929
|
-
|
|
940
|
+
function engineUpdate(frameTimeMS=0)
|
|
930
941
|
{
|
|
931
942
|
// update time keeping
|
|
932
943
|
let frameTimeDeltaMS = frameTimeMS - frameTimeLastMS;
|
|
933
944
|
frameTimeLastMS = frameTimeMS;
|
|
934
945
|
if (debug || showWatermark)
|
|
935
|
-
averageFPS = lerp(.05, averageFPS
|
|
946
|
+
averageFPS = lerp(.05, averageFPS, 1e3/(frameTimeDeltaMS||1));
|
|
936
947
|
const debugSpeedUp = debug && keyIsDown(107); // +
|
|
937
948
|
const debugSpeedDown = debug && keyIsDown(109); // -
|
|
938
949
|
if (debug)
|
|
@@ -944,24 +955,19 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
944
955
|
|
|
945
956
|
if (canvasFixedSize.x)
|
|
946
957
|
{
|
|
947
|
-
// clear set fixed size
|
|
958
|
+
// clear canvas and set fixed size
|
|
948
959
|
overlayCanvas.width = mainCanvas.width = canvasFixedSize.x;
|
|
949
960
|
overlayCanvas.height = mainCanvas.height = canvasFixedSize.y;
|
|
950
961
|
|
|
951
962
|
// fit to window by adding space on top or bottom if necessary
|
|
952
963
|
const aspect = innerWidth / innerHeight;
|
|
953
964
|
const fixedAspect = mainCanvas.width / mainCanvas.height;
|
|
954
|
-
mainCanvas.style.width
|
|
955
|
-
mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
|
|
956
|
-
if (glCanvas)
|
|
957
|
-
{
|
|
958
|
-
glCanvas.style.width = mainCanvas.style.width;
|
|
959
|
-
glCanvas.style.height = mainCanvas.style.height;
|
|
960
|
-
}
|
|
965
|
+
(glCanvas||mainCanvas).style.width = mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
|
|
966
|
+
(glCanvas||mainCanvas).style.height = mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
|
|
961
967
|
}
|
|
962
968
|
else
|
|
963
969
|
{
|
|
964
|
-
|
|
970
|
+
// clear canvas and set size to same as window
|
|
965
971
|
overlayCanvas.width = mainCanvas.width = min(innerWidth, canvasMaxSize.x);
|
|
966
972
|
overlayCanvas.height = mainCanvas.height = min(innerHeight, canvasMaxSize.y);
|
|
967
973
|
}
|
|
@@ -1028,7 +1034,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
1028
1034
|
overlayContext.fillStyle = '#000';
|
|
1029
1035
|
const text = engineName + ' ' + 'v' + engineVersion + ' / '
|
|
1030
1036
|
+ drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
|
|
1031
|
-
+
|
|
1037
|
+
+ (glEnable ? ' GL' : ' 2D') ;
|
|
1032
1038
|
overlayContext.fillText(text, mainCanvas.width-3, 3);
|
|
1033
1039
|
overlayContext.fillStyle = '#fff';
|
|
1034
1040
|
overlayContext.fillText(text, mainCanvas.width-2, 2);
|
|
@@ -1042,7 +1048,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
1042
1048
|
tileImageSource ? tileImage.src = tileImageSource : tileImage.onload();
|
|
1043
1049
|
}
|
|
1044
1050
|
|
|
1045
|
-
//
|
|
1051
|
+
// Called automatically by engine to setup render system
|
|
1046
1052
|
function enginePreRender()
|
|
1047
1053
|
{
|
|
1048
1054
|
// save canvas size
|
|
@@ -1052,12 +1058,10 @@ function enginePreRender()
|
|
|
1052
1058
|
mainContext.imageSmoothingEnabled = !cavasPixelated;
|
|
1053
1059
|
|
|
1054
1060
|
// setup gl rendering if enabled
|
|
1055
|
-
glEnable && glPreRender(
|
|
1061
|
+
glEnable && glPreRender();
|
|
1056
1062
|
}
|
|
1057
1063
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
/** Calls update on each engine object (recursively if child), removes destroyed objects, and updated time */
|
|
1064
|
+
/** Update each engine object, remove destroyed objects, and update time */
|
|
1061
1065
|
function engineObjectsUpdate()
|
|
1062
1066
|
{
|
|
1063
1067
|
// get list of solid objects for physics optimzation
|
|
@@ -1160,7 +1164,7 @@ class EngineObject
|
|
|
1160
1164
|
constructor(pos=vec2(), size=objectDefaultSize, tileIndex=-1, tileSize=tileSizeDefault, angle=0, color, renderOrder=0)
|
|
1161
1165
|
{
|
|
1162
1166
|
// set passed in params
|
|
1163
|
-
ASSERT(pos &&
|
|
1167
|
+
ASSERT(isVector2(pos) && isVector2(size)); // ensure pos and size are vec2s
|
|
1164
1168
|
|
|
1165
1169
|
/** @property {Vector2} - World space position of the object */
|
|
1166
1170
|
this.pos = pos.copy();
|
|
@@ -1250,7 +1254,7 @@ class EngineObject
|
|
|
1250
1254
|
if (this.collideSolidObjects)
|
|
1251
1255
|
{
|
|
1252
1256
|
// check collisions against solid objects
|
|
1253
|
-
const epsilon =
|
|
1257
|
+
const epsilon = .001; // necessary to push slightly outside of the collision
|
|
1254
1258
|
for (const o of engineObjectsCollide)
|
|
1255
1259
|
{
|
|
1256
1260
|
// non solid objects don't collide with eachother
|
|
@@ -1513,11 +1517,6 @@ class EngineObject
|
|
|
1513
1517
|
|
|
1514
1518
|
'use strict';
|
|
1515
1519
|
|
|
1516
|
-
/** Tile sheet for batch rendering system
|
|
1517
|
-
* @type {Image}
|
|
1518
|
-
* @memberof Draw */
|
|
1519
|
-
const tileImage = new Image();
|
|
1520
|
-
|
|
1521
1520
|
/** The primary 2D canvas visible to the user
|
|
1522
1521
|
* @type {HTMLCanvasElement}
|
|
1523
1522
|
* @memberof Draw */
|
|
@@ -1543,6 +1542,14 @@ let overlayContext;
|
|
|
1543
1542
|
* @memberof Draw */
|
|
1544
1543
|
let mainCanvasSize = vec2();
|
|
1545
1544
|
|
|
1545
|
+
/** Tile sheet for batch rendering system
|
|
1546
|
+
* @type {Image}
|
|
1547
|
+
* @memberof Draw */
|
|
1548
|
+
const tileImage = new Image;
|
|
1549
|
+
|
|
1550
|
+
// Engine internal variables not exposed to documentation
|
|
1551
|
+
let tileImageSize, tileImageFixBleed, drawCount;
|
|
1552
|
+
|
|
1546
1553
|
/** Convert from screen to world space coordinates
|
|
1547
1554
|
* - if calling outside of render, you may need to manually set mainCanvasSize
|
|
1548
1555
|
* @param {Vector2} screenPos
|
|
@@ -1567,7 +1574,7 @@ const worldToScreen = (worldPos)=>
|
|
|
1567
1574
|
|
|
1568
1575
|
/** Draw textured tile centered in world space, with color applied if using WebGL
|
|
1569
1576
|
* @param {Vector2} pos - Center of the tile in world space
|
|
1570
|
-
* @param {Vector2} [size=new Vector2(1,1)] - Size of the tile in world space
|
|
1577
|
+
* @param {Vector2} [size=new Vector2(1,1)] - Size of the tile in world space
|
|
1571
1578
|
* @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
|
|
1572
1579
|
* @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
|
|
1573
1580
|
* @param {Color} [color=new Color(1,1,1)] - Color to modulate with
|
|
@@ -1786,10 +1793,10 @@ class FontImage
|
|
|
1786
1793
|
*/
|
|
1787
1794
|
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), startTileIndex=0, context=overlayContext)
|
|
1788
1795
|
{
|
|
1789
|
-
if (!
|
|
1796
|
+
if (!engineFontImage)
|
|
1790
1797
|
{
|
|
1791
1798
|
// load default font image
|
|
1792
|
-
engineFontImage = new Image
|
|
1799
|
+
engineFontImage = new Image;
|
|
1793
1800
|
engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
|
|
1794
1801
|
}
|
|
1795
1802
|
|
|
@@ -1856,7 +1863,7 @@ class FontImage
|
|
|
1856
1863
|
/** Returns true if fullscreen mode is active
|
|
1857
1864
|
* @return {Boolean}
|
|
1858
1865
|
* @memberof Draw */
|
|
1859
|
-
const isFullscreen =()=> document.fullscreenElement;
|
|
1866
|
+
const isFullscreen = ()=> document.fullscreenElement;
|
|
1860
1867
|
|
|
1861
1868
|
/** Toggle fullsceen mode
|
|
1862
1869
|
* @memberof Draw */
|
|
@@ -1866,16 +1873,9 @@ function toggleFullscreen()
|
|
|
1866
1873
|
{
|
|
1867
1874
|
if (document.exitFullscreen)
|
|
1868
1875
|
document.exitFullscreen();
|
|
1869
|
-
else if (document.mozCancelFullScreen)
|
|
1870
|
-
document.mozCancelFullScreen();
|
|
1871
|
-
}
|
|
1872
|
-
else
|
|
1873
|
-
{
|
|
1874
|
-
if (document.body.webkitRequestFullScreen)
|
|
1875
|
-
document.body.webkitRequestFullScreen();
|
|
1876
|
-
else if (document.body.mozRequestFullScreen)
|
|
1877
|
-
document.body.mozRequestFullScreen();
|
|
1878
1876
|
}
|
|
1877
|
+
else if (document.body.requestFullscreen)
|
|
1878
|
+
document.body.requestFullscreen();
|
|
1879
1879
|
}
|
|
1880
1880
|
|
|
1881
1881
|
/**
|
|
@@ -1894,7 +1894,7 @@ function toggleFullscreen()
|
|
|
1894
1894
|
* @param {Number} [device=0]
|
|
1895
1895
|
* @return {Boolean}
|
|
1896
1896
|
* @memberof Input */
|
|
1897
|
-
const keyIsDown = (key, device=0)=> inputData[device] && inputData[device][key] & 1
|
|
1897
|
+
const keyIsDown = (key, device=0)=> inputData[device] && inputData[device][key] & 1;
|
|
1898
1898
|
|
|
1899
1899
|
/** Returns true if device key was pressed this frame
|
|
1900
1900
|
* @param {Number} key
|
|
@@ -2134,35 +2134,40 @@ const isTouchDevice = window.ontouchstart !== undefined;
|
|
|
2134
2134
|
if (isTouchDevice)
|
|
2135
2135
|
{
|
|
2136
2136
|
// override mouse events
|
|
2137
|
-
|
|
2138
|
-
onmousedown = onmouseup =
|
|
2137
|
+
let wasTouching, mouseDown = onmousedown, mouseUp = onmouseup;
|
|
2138
|
+
onmousedown = onmouseup = ()=> 0;
|
|
2139
2139
|
|
|
2140
|
-
//
|
|
2141
|
-
|
|
2142
|
-
ontouchstart = ontouchmove = ontouchend = (e)=>
|
|
2140
|
+
// setup touch input
|
|
2141
|
+
ontouchstart = (e)=>
|
|
2143
2142
|
{
|
|
2144
|
-
|
|
2143
|
+
// fix mobile audio, force it to play a sound on first touch
|
|
2144
|
+
zzfx(0);
|
|
2145
2145
|
|
|
2146
|
-
//
|
|
2147
|
-
|
|
2148
|
-
if (touching)
|
|
2146
|
+
// handle all touch events the same way
|
|
2147
|
+
ontouchstart = ontouchmove = ontouchend = (e)=>
|
|
2149
2148
|
{
|
|
2150
|
-
|
|
2151
|
-
hadTouch || zzfx(0, hadTouch=1);
|
|
2149
|
+
e.button = 0; // all touches are left click
|
|
2152
2150
|
|
|
2153
|
-
//
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2151
|
+
// check if touching and pass to mouse events
|
|
2152
|
+
const touching = e.touches.length;
|
|
2153
|
+
if (touching)
|
|
2154
|
+
{
|
|
2155
|
+
// set event pos and pass it along
|
|
2156
|
+
e.x = e.touches[0].clientX;
|
|
2157
|
+
e.y = e.touches[0].clientY;
|
|
2158
|
+
wasTouching ? onmousemove(e) : mouseDown(e);
|
|
2159
|
+
}
|
|
2160
|
+
else if (wasTouching)
|
|
2161
|
+
mouseUp(e);
|
|
2160
2162
|
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
+
// set was touching
|
|
2164
|
+
wasTouching = touching;
|
|
2163
2165
|
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
+
// must return true so the document will get focus
|
|
2167
|
+
return true;
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
return ontouchstart(e);
|
|
2166
2171
|
}
|
|
2167
2172
|
}
|
|
2168
2173
|
|
|
@@ -2170,7 +2175,7 @@ if (isTouchDevice)
|
|
|
2170
2175
|
// touch gamepad, virtual on screen gamepad emulator for touch devices
|
|
2171
2176
|
|
|
2172
2177
|
// touch input internal variables
|
|
2173
|
-
let touchGamepadTimer = new Timer, touchGamepadButtons
|
|
2178
|
+
let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
|
|
2174
2179
|
|
|
2175
2180
|
// create the touch gamepad, called automatically by the engine
|
|
2176
2181
|
function touchGamepadCreate()
|
|
@@ -2178,65 +2183,73 @@ function touchGamepadCreate()
|
|
|
2178
2183
|
if (!touchGamepadEnable || !isTouchDevice)
|
|
2179
2184
|
return;
|
|
2180
2185
|
|
|
2181
|
-
|
|
2186
|
+
// touch input internal variables
|
|
2187
|
+
touchGamepadButtons = [];
|
|
2188
|
+
touchGamepadStick = vec2();
|
|
2189
|
+
|
|
2190
|
+
// setup touch input
|
|
2191
|
+
ontouchstart = (e)=>
|
|
2182
2192
|
{
|
|
2183
|
-
|
|
2184
|
-
|
|
2193
|
+
// fix mobile audio, force it to play a sound on first touch
|
|
2194
|
+
zzfx(0);
|
|
2185
2195
|
|
|
2186
|
-
|
|
2187
|
-
touchGamepadStick = vec2();
|
|
2188
|
-
touchGamepadButtons = [];
|
|
2189
|
-
|
|
2190
|
-
const touching = e.touches.length;
|
|
2191
|
-
if (touching)
|
|
2196
|
+
ontouchstart = ontouchmove = ontouchend = (e)=>
|
|
2192
2197
|
{
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
if (paused)
|
|
2198
|
+
// clear touch gamepad input
|
|
2199
|
+
touchGamepadStick = vec2();
|
|
2200
|
+
touchGamepadButtons = [];
|
|
2201
|
+
|
|
2202
|
+
const touching = e.touches.length;
|
|
2203
|
+
if (touching)
|
|
2200
2204
|
{
|
|
2201
|
-
//
|
|
2202
|
-
|
|
2203
|
-
|
|
2205
|
+
// set that gamepad is active
|
|
2206
|
+
isUsingGamepad = 1;
|
|
2207
|
+
touchGamepadTimer.set();
|
|
2208
|
+
|
|
2209
|
+
if (paused)
|
|
2210
|
+
{
|
|
2211
|
+
// touch anywhere to press start when paused
|
|
2212
|
+
touchGamepadButtons[9] = 1;
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2204
2215
|
}
|
|
2205
|
-
}
|
|
2206
2216
|
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2217
|
+
// get center of left and right sides
|
|
2218
|
+
const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
2219
|
+
const buttonCenter = mainCanvasSize.subtract(vec2(touchGamepadSize, touchGamepadSize));
|
|
2220
|
+
const startCenter = mainCanvasSize.scale(.5);
|
|
2211
2221
|
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
{
|
|
2215
|
-
const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
|
|
2216
|
-
if (touchPos.distance(stickCenter) < touchGamepadSize)
|
|
2222
|
+
// check each touch point
|
|
2223
|
+
for (const touch of e.touches)
|
|
2217
2224
|
{
|
|
2218
|
-
|
|
2219
|
-
if (
|
|
2220
|
-
|
|
2221
|
-
|
|
2225
|
+
const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
|
|
2226
|
+
if (touchPos.distance(stickCenter) < touchGamepadSize)
|
|
2227
|
+
{
|
|
2228
|
+
// virtual analog stick
|
|
2229
|
+
if (touchGamepadAnalog)
|
|
2230
|
+
touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
|
|
2231
|
+
else
|
|
2232
|
+
{
|
|
2233
|
+
// 8 way dpad
|
|
2234
|
+
const angle = touchPos.subtract(stickCenter).angle();
|
|
2235
|
+
touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
else if (touchPos.distance(buttonCenter) < touchGamepadSize)
|
|
2222
2239
|
{
|
|
2223
|
-
//
|
|
2224
|
-
const
|
|
2225
|
-
|
|
2240
|
+
// virtual face buttons
|
|
2241
|
+
const button = touchPos.subtract(buttonCenter).direction();
|
|
2242
|
+
touchGamepadButtons[button] = 1;
|
|
2243
|
+
}
|
|
2244
|
+
else if (touchPos.distance(startCenter) < touchGamepadSize)
|
|
2245
|
+
{
|
|
2246
|
+
// virtual start button in center
|
|
2247
|
+
touchGamepadButtons[9] = 1;
|
|
2226
2248
|
}
|
|
2227
|
-
}
|
|
2228
|
-
else if (touchPos.distance(buttonCenter) < touchGamepadSize)
|
|
2229
|
-
{
|
|
2230
|
-
// virtual face buttons
|
|
2231
|
-
const button = touchPos.subtract(buttonCenter).direction();
|
|
2232
|
-
touchGamepadButtons[button] = 1;
|
|
2233
|
-
}
|
|
2234
|
-
else if (touchPos.distance(startCenter) < touchGamepadSize)
|
|
2235
|
-
{
|
|
2236
|
-
// virtual start button in center
|
|
2237
|
-
touchGamepadButtons[9] = 1;
|
|
2238
2249
|
}
|
|
2239
2250
|
}
|
|
2251
|
+
|
|
2252
|
+
return ontouchstart(e);
|
|
2240
2253
|
}
|
|
2241
2254
|
}
|
|
2242
2255
|
|
|
@@ -2354,7 +2367,7 @@ class Sound
|
|
|
2354
2367
|
{
|
|
2355
2368
|
if (!soundEnable) return;
|
|
2356
2369
|
|
|
2357
|
-
let pan
|
|
2370
|
+
let pan;
|
|
2358
2371
|
if (pos)
|
|
2359
2372
|
{
|
|
2360
2373
|
const range = this.range;
|
|
@@ -2384,7 +2397,7 @@ class Sound
|
|
|
2384
2397
|
* @param {Number} [volume=1] - How much to scale volume by (in addition to range fade)
|
|
2385
2398
|
* @return {AudioBufferSourceNode} - The audio, can be used to stop sound later
|
|
2386
2399
|
*/
|
|
2387
|
-
playNote(semitoneOffset, pos, volume
|
|
2400
|
+
playNote(semitoneOffset, pos, volume)
|
|
2388
2401
|
{
|
|
2389
2402
|
if (!soundEnable) return;
|
|
2390
2403
|
|
|
@@ -2439,7 +2452,7 @@ class Music
|
|
|
2439
2452
|
* @param {Boolean} [loop=1] - True if the music should loop when it reaches the end
|
|
2440
2453
|
* @return {AudioBufferSourceNode} - The audio node, can be used to stop sound later
|
|
2441
2454
|
*/
|
|
2442
|
-
play(volume
|
|
2455
|
+
play(volume, loop = 1)
|
|
2443
2456
|
{
|
|
2444
2457
|
if (!soundEnable) return;
|
|
2445
2458
|
|
|
@@ -2521,7 +2534,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
|
2521
2534
|
|
|
2522
2535
|
// create audio context
|
|
2523
2536
|
if (!audioContext)
|
|
2524
|
-
audioContext = new
|
|
2537
|
+
audioContext = new AudioContext;
|
|
2525
2538
|
|
|
2526
2539
|
// fix stalled audio
|
|
2527
2540
|
audioContext.resume();
|
|
@@ -2540,18 +2553,13 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
|
2540
2553
|
source.playbackRate.value = rate;
|
|
2541
2554
|
source.loop = loop;
|
|
2542
2555
|
|
|
2543
|
-
// create and connect gain node (createGain is more
|
|
2556
|
+
// create and connect gain node (createGain is more widely spported then GainNode construtor)
|
|
2544
2557
|
const gainNode = audioContext.createGain();
|
|
2545
2558
|
gainNode.gain.value = soundVolume*volume;
|
|
2546
2559
|
gainNode.connect(audioContext.destination);
|
|
2547
2560
|
|
|
2548
|
-
// connect source to gain
|
|
2549
|
-
(
|
|
2550
|
-
window.StereoPannerNode ? // create pan node if possible
|
|
2551
|
-
source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)}))
|
|
2552
|
-
: source
|
|
2553
|
-
)
|
|
2554
|
-
.connect(gainNode);
|
|
2561
|
+
// connect source to stereo panner and gain
|
|
2562
|
+
source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)})).connect(gainNode);
|
|
2555
2563
|
|
|
2556
2564
|
// play and return sound
|
|
2557
2565
|
source.start();
|
|
@@ -2669,10 +2677,8 @@ function zzfxG
|
|
|
2669
2677
|
* @memberof Audio */
|
|
2670
2678
|
function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
2671
2679
|
{
|
|
2680
|
+
let i, j, k;
|
|
2672
2681
|
let instrumentParameters;
|
|
2673
|
-
let i;
|
|
2674
|
-
let j;
|
|
2675
|
-
let k;
|
|
2676
2682
|
let note;
|
|
2677
2683
|
let sample;
|
|
2678
2684
|
let patternChannel;
|
|
@@ -2842,14 +2848,11 @@ function tileCollisionRaycast(posStart, posEnd, object)
|
|
|
2842
2848
|
{
|
|
2843
2849
|
// test if a ray collides with tiles from start to end
|
|
2844
2850
|
// todo: a way to get the exact hit point, it must still register as inside the hit tile
|
|
2845
|
-
posStart = posStart.floor();
|
|
2846
|
-
posEnd = posEnd.floor();
|
|
2847
|
-
const posDelta = posEnd.subtract(posStart);
|
|
2851
|
+
const posDelta = (posEnd = posEnd.floor()).subtract(posStart = posStart.floor());
|
|
2848
2852
|
const dx = abs(posDelta.x), dy = -abs(posDelta.y);
|
|
2849
2853
|
const sx = sign(posDelta.x), sy = sign(posDelta.y);
|
|
2850
|
-
let e = dx + dy;
|
|
2851
2854
|
|
|
2852
|
-
for (let x = posStart.x, y = posStart.y;;)
|
|
2855
|
+
for (let x = posStart.x, y = posStart.y, e = dx + dy;;)
|
|
2853
2856
|
{
|
|
2854
2857
|
const tileData = getTileCollisionData(vec2(x,y));
|
|
2855
2858
|
if (tileData && (object ? object.collideWithTileRaycast(tileData, new Vector2(x, y)) : tileData > 0))
|
|
@@ -2941,7 +2944,7 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
|
|
|
2941
2944
|
// init tile data
|
|
2942
2945
|
this.data = [];
|
|
2943
2946
|
for (let j = this.size.area(); j--;)
|
|
2944
|
-
this.data.push(new TileLayerData
|
|
2947
|
+
this.data.push(new TileLayerData);
|
|
2945
2948
|
}
|
|
2946
2949
|
|
|
2947
2950
|
/** Set data at a given position in the array
|
|
@@ -2997,21 +3000,23 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
|
|
|
2997
3000
|
* @param {Boolean} [clear=0] - Should it clear the canvas before drawing */
|
|
2998
3001
|
redrawStart(clear = 0)
|
|
2999
3002
|
{
|
|
3000
|
-
if (clear)
|
|
3001
|
-
{
|
|
3002
|
-
// clear and set size
|
|
3003
|
-
this.canvas.width = this.size.x * this.tileSize.x;
|
|
3004
|
-
this.canvas.height = this.size.y * this.tileSize.y;
|
|
3005
|
-
}
|
|
3006
|
-
|
|
3007
3003
|
// save current render settings
|
|
3008
|
-
this.savedRenderSettings = [mainCanvas, mainContext, cameraPos, cameraScale];
|
|
3004
|
+
this.savedRenderSettings = [mainCanvas, mainContext, mainCanvasSize, cameraPos, cameraScale];
|
|
3009
3005
|
|
|
3010
|
-
// use normal rendering system to render the tiles
|
|
3006
|
+
// hack: use normal rendering system to render the tiles
|
|
3011
3007
|
mainCanvas = this.canvas;
|
|
3012
3008
|
mainContext = this.context;
|
|
3013
3009
|
cameraPos = this.size.scale(.5);
|
|
3014
3010
|
cameraScale = this.tileSize.x;
|
|
3011
|
+
|
|
3012
|
+
if (clear)
|
|
3013
|
+
{
|
|
3014
|
+
// clear and set size
|
|
3015
|
+
mainCanvas.width = this.size.x * this.tileSize.x;
|
|
3016
|
+
mainCanvas.height = this.size.y * this.tileSize.y;
|
|
3017
|
+
}
|
|
3018
|
+
|
|
3019
|
+
// begin a new render for the tile canvas
|
|
3015
3020
|
enginePreRender();
|
|
3016
3021
|
}
|
|
3017
3022
|
|
|
@@ -3023,7 +3028,7 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
|
|
|
3023
3028
|
//debugSaveCanvas(this.canvas);
|
|
3024
3029
|
|
|
3025
3030
|
// set stuff back to normal
|
|
3026
|
-
[mainCanvas, mainContext, cameraPos, cameraScale] = this.savedRenderSettings;
|
|
3031
|
+
[mainCanvas, mainContext, mainCanvasSize, cameraPos, cameraScale] = this.savedRenderSettings;
|
|
3027
3032
|
}
|
|
3028
3033
|
|
|
3029
3034
|
/** Draw the tile at a given position
|
|
@@ -3264,7 +3269,7 @@ class ParticleEmitter extends EngineObject
|
|
|
3264
3269
|
this.parent && super.update();
|
|
3265
3270
|
|
|
3266
3271
|
// update emitter
|
|
3267
|
-
if (!this.emitTime
|
|
3272
|
+
if (!this.emitTime | this.getAliveTime() <= this.emitTime)
|
|
3268
3273
|
{
|
|
3269
3274
|
// emit particles
|
|
3270
3275
|
if (this.emitRate * particleEmitRateScale)
|
|
@@ -3286,9 +3291,9 @@ class ParticleEmitter extends EngineObject
|
|
|
3286
3291
|
{
|
|
3287
3292
|
// spawn a particle
|
|
3288
3293
|
let pos = this.emitSize.x != undefined ? // check if vec2 was used for size
|
|
3289
|
-
(new Vector2(rand(
|
|
3290
|
-
.multiply(this.emitSize).rotate(this.angle) // box emitter
|
|
3291
|
-
: randInCircle(this.emitSize
|
|
3294
|
+
(new Vector2(rand(-1,1), rand(-1,1)))
|
|
3295
|
+
.multiply(this.emitSize/2).rotate(this.angle) // box emitter
|
|
3296
|
+
: randInCircle(this.emitSize/2); // circle emitter
|
|
3292
3297
|
let angle = rand(this.particleConeAngle, -this.particleConeAngle);
|
|
3293
3298
|
if (!this.localSpace)
|
|
3294
3299
|
{
|
|
@@ -3331,7 +3336,7 @@ class ParticleEmitter extends EngineObject
|
|
|
3331
3336
|
particle.additive = this.additive;
|
|
3332
3337
|
particle.renderOrder = this.renderOrder;
|
|
3333
3338
|
particle.trailScale = this.trailScale;
|
|
3334
|
-
particle.mirror =
|
|
3339
|
+
particle.mirror = randInt(2);
|
|
3335
3340
|
particle.localSpaceEmitter = this.localSpace && this;
|
|
3336
3341
|
|
|
3337
3342
|
// setup callbacks for particles
|
|
@@ -3360,7 +3365,8 @@ class Particle extends EngineObject
|
|
|
3360
3365
|
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
|
|
3361
3366
|
* @param {Number} [angle=0] - Angle to rotate the particle
|
|
3362
3367
|
*/
|
|
3363
|
-
constructor(pos, tileIndex, tileSize, angle)
|
|
3368
|
+
constructor(pos, tileIndex, tileSize, angle)
|
|
3369
|
+
{ super(pos, new Vector2, tileIndex, tileSize, angle); }
|
|
3364
3370
|
|
|
3365
3371
|
/** Render the particle, automatically called each frame, sorted by renderOrder */
|
|
3366
3372
|
render()
|
|
@@ -3480,7 +3486,7 @@ class Medal
|
|
|
3480
3486
|
this.name = name;
|
|
3481
3487
|
this.description = description;
|
|
3482
3488
|
this.icon = icon;
|
|
3483
|
-
this.image = new Image
|
|
3489
|
+
this.image = new Image;
|
|
3484
3490
|
if (src)
|
|
3485
3491
|
this.image.src = src;
|
|
3486
3492
|
}
|
|
@@ -3495,10 +3501,7 @@ class Medal
|
|
|
3495
3501
|
ASSERT(medalsSaveName); // save name must be set
|
|
3496
3502
|
localStorage[this.storageKey()] = this.unlocked = 1;
|
|
3497
3503
|
medalsDisplayQueue.push(this);
|
|
3498
|
-
|
|
3499
|
-
// save for newgrounds and OS13K
|
|
3500
3504
|
newgrounds && newgrounds.unlockMedal(this.id);
|
|
3501
|
-
localStorage['OS13kTrophy,' + this.icon + ',' + medalsSaveName + ',' + this.name] = this.description;
|
|
3502
3505
|
}
|
|
3503
3506
|
|
|
3504
3507
|
/** Render a medal
|
|
@@ -3514,20 +3517,19 @@ class Medal
|
|
|
3514
3517
|
// draw containing rect and clip to that region
|
|
3515
3518
|
context.save();
|
|
3516
3519
|
context.beginPath();
|
|
3517
|
-
context.fillStyle =
|
|
3518
|
-
context.
|
|
3519
|
-
context.strokeStyle = '#000';
|
|
3520
|
+
context.fillStyle = new Color(.9,.9,.9);
|
|
3521
|
+
context.strokeStyle = new Color(0,0,0);
|
|
3520
3522
|
context.lineWidth = 3;
|
|
3523
|
+
context.fill(context.rect(x, y, width, medalDisplayHeight));
|
|
3521
3524
|
context.stroke();
|
|
3522
3525
|
context.clip();
|
|
3523
3526
|
|
|
3524
3527
|
// draw the icon and text
|
|
3525
|
-
this.renderIcon(x+15+medalDisplayIconSize/2, y+medalDisplayHeight/2);
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
context.fillText(this.description, x+medalDisplayIconSize+30, y+60);
|
|
3528
|
+
this.renderIcon(vec2(x+15+medalDisplayIconSize/2, y+medalDisplayHeight/2));
|
|
3529
|
+
const pos = vec2(x+medalDisplayIconSize+30, y+28);
|
|
3530
|
+
drawTextScreen(this.name, pos, 38, new Color(0,0,0), 0, 0, 'left');
|
|
3531
|
+
pos.y += 32;
|
|
3532
|
+
drawTextScreen(this.description, pos, 24, new Color(0,0,0), 0, 0, 'left');
|
|
3531
3533
|
context.restore();
|
|
3532
3534
|
}
|
|
3533
3535
|
|
|
@@ -3536,18 +3538,13 @@ class Medal
|
|
|
3536
3538
|
* @param {Number} y - Screen space Y position
|
|
3537
3539
|
* @param {Number} [size=medalDisplayIconSize] - Screen space size
|
|
3538
3540
|
*/
|
|
3539
|
-
renderIcon(
|
|
3541
|
+
renderIcon(pos, size=medalDisplayIconSize)
|
|
3540
3542
|
{
|
|
3541
3543
|
// draw the image or icon
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
context.textAlign = 'center';
|
|
3545
|
-
context.textBaseline = 'middle';
|
|
3546
|
-
context.font = size*.7 + 'px '+ fontDefault;
|
|
3547
|
-
if (this.image.src)
|
|
3548
|
-
context.drawImage(this.image, x-size/2, y-size/2, size, size);
|
|
3544
|
+
if (this.image)
|
|
3545
|
+
overlayContext.drawImage(this.image, pos.x-size/2, pos.y-size/2, size, size);
|
|
3549
3546
|
else
|
|
3550
|
-
|
|
3547
|
+
drawTextScreen(this.icon, pos, size*.7, new Color(0,0,0));
|
|
3551
3548
|
}
|
|
3552
3549
|
|
|
3553
3550
|
// Get local storage key used by the medal
|
|
@@ -3605,13 +3602,21 @@ class Newgrounds
|
|
|
3605
3602
|
constructor(app_id, cipher)
|
|
3606
3603
|
{
|
|
3607
3604
|
ASSERT(!newgrounds && app_id);
|
|
3605
|
+
|
|
3606
|
+
// create an instance of CryptoJS for encrypted calls
|
|
3607
|
+
if (cipher)
|
|
3608
|
+
{
|
|
3609
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3610
|
+
// Crypto-JS - https://github.com/brix/crypto-js [The MIT License (MIT)]
|
|
3611
|
+
// Copyright (c) 2009-2013 Jeff Mott Copyright (c) 2013-2016 Evan Vosberg
|
|
3612
|
+
|
|
3613
|
+
this.cryptoJS = 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));
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3608
3616
|
this.app_id = app_id;
|
|
3609
3617
|
this.cipher = cipher;
|
|
3610
3618
|
this.host = location ? location.hostname : '';
|
|
3611
3619
|
|
|
3612
|
-
// create an instance of CryptoJS for encrypted calls
|
|
3613
|
-
cipher && (this.cryptoJS = CryptoJS());
|
|
3614
|
-
|
|
3615
3620
|
// get session id from url search params
|
|
3616
3621
|
const url = new URL(location.href);
|
|
3617
3622
|
this.session_id = url.searchParams.get('ngio_session_id') || 0;
|
|
@@ -3714,12 +3719,6 @@ class Newgrounds
|
|
|
3714
3719
|
return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
|
|
3715
3720
|
}
|
|
3716
3721
|
}
|
|
3717
|
-
|
|
3718
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
3719
|
-
// Crypto-JS - https://github.com/brix/crypto-js [The MIT License (MIT)]
|
|
3720
|
-
// Copyright (c) 2009-2013 Jeff Mott Copyright (c) 2013-2016 Evan Vosberg
|
|
3721
|
-
|
|
3722
|
-
const CryptoJS=()=>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));
|
|
3723
3722
|
/**
|
|
3724
3723
|
* LittleJS WebGL Interface
|
|
3725
3724
|
* <br> - All webgl used by the engine is wrapped up here
|
|
@@ -3749,7 +3748,7 @@ let glContext;
|
|
|
3749
3748
|
let glTileTexture;
|
|
3750
3749
|
|
|
3751
3750
|
// WebGL internal variables not exposed to documentation
|
|
3752
|
-
let glActiveTexture, glShader, glArrayBuffer,
|
|
3751
|
+
let glActiveTexture, glShader, glArrayBuffer, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
|
|
3753
3752
|
|
|
3754
3753
|
///////////////////////////////////////////////////////////////////////////////
|
|
3755
3754
|
|
|
@@ -3770,27 +3769,25 @@ function glInit()
|
|
|
3770
3769
|
'uniform mat4 m;'+ // transform matrix
|
|
3771
3770
|
'attribute vec2 p,t;'+ // position, uv
|
|
3772
3771
|
'attribute vec4 c,a;'+ // color, additiveColor
|
|
3773
|
-
'varying
|
|
3774
|
-
'varying vec4 d,e;'+ // return color, additiveColor
|
|
3772
|
+
'varying vec4 v,d,e;'+ // return uv, color, additiveColor
|
|
3775
3773
|
'void main(){'+ // shader entry point
|
|
3776
3774
|
'gl_Position=m*vec4(p,1,1);'+ // transform position
|
|
3777
|
-
'v=t;d=c;e=a;'+
|
|
3775
|
+
'v=vec4(t,p);d=c;e=a;'+ // pass stuff to fragment shader
|
|
3778
3776
|
'}' // end of shader
|
|
3779
3777
|
,
|
|
3780
|
-
'precision highp float;'+
|
|
3781
|
-
'varying
|
|
3782
|
-
'
|
|
3783
|
-
'
|
|
3784
|
-
'
|
|
3785
|
-
'
|
|
3786
|
-
'}' // end of shader
|
|
3778
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
3779
|
+
'varying vec4 v,d,e;'+ // uv, color, additiveColor
|
|
3780
|
+
'uniform sampler2D s;'+ // texture
|
|
3781
|
+
'void main(){'+ // shader entry point
|
|
3782
|
+
'gl_FragColor=texture2D(s,v.xy)*d+e;'+ // modulate texture by color plus additive
|
|
3783
|
+
'}' // end of shader
|
|
3787
3784
|
);
|
|
3788
3785
|
|
|
3789
3786
|
// init buffers
|
|
3790
|
-
|
|
3787
|
+
const vertexData = new ArrayBuffer(gl_VERTEX_BUFFER_SIZE);
|
|
3791
3788
|
glArrayBuffer = glContext.createBuffer();
|
|
3792
|
-
glPositionData = new Float32Array(
|
|
3793
|
-
glColorData = new Uint32Array(
|
|
3789
|
+
glPositionData = new Float32Array(vertexData);
|
|
3790
|
+
glColorData = new Uint32Array(vertexData);
|
|
3794
3791
|
glBatchCount = 0;
|
|
3795
3792
|
}
|
|
3796
3793
|
|
|
@@ -3865,18 +3862,19 @@ function glCreateTexture(image)
|
|
|
3865
3862
|
image && image.width && glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
|
|
3866
3863
|
|
|
3867
3864
|
// use point filtering for pixelated rendering
|
|
3868
|
-
|
|
3869
|
-
glContext.texParameteri(gl_TEXTURE_2D,
|
|
3865
|
+
const filter = cavasPixelated ? gl_NEAREST : gl_LINEAR;
|
|
3866
|
+
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
|
|
3867
|
+
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, filter);
|
|
3870
3868
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE);
|
|
3871
3869
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE);
|
|
3872
3870
|
return texture;
|
|
3873
3871
|
}
|
|
3874
3872
|
|
|
3875
3873
|
// called automatically by engine before render
|
|
3876
|
-
function glPreRender(
|
|
3874
|
+
function glPreRender()
|
|
3877
3875
|
{
|
|
3878
3876
|
// clear and set to same size as main canvas
|
|
3879
|
-
glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height);
|
|
3877
|
+
glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
|
|
3880
3878
|
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
3881
3879
|
|
|
3882
3880
|
// set up the shader
|
|
@@ -3884,7 +3882,7 @@ function glPreRender(width, height, cameraX, cameraY, cameraScale)
|
|
|
3884
3882
|
glContext.activeTexture(gl_TEXTURE0);
|
|
3885
3883
|
glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
|
|
3886
3884
|
glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
|
|
3887
|
-
glContext.bufferData(gl_ARRAY_BUFFER,
|
|
3885
|
+
glContext.bufferData(gl_ARRAY_BUFFER, gl_VERTEX_BUFFER_SIZE, gl_DYNAMIC_DRAW);
|
|
3888
3886
|
glSetBlendMode();
|
|
3889
3887
|
|
|
3890
3888
|
// set vertex attributes
|
|
@@ -3902,14 +3900,14 @@ function glPreRender(width, height, cameraX, cameraY, cameraScale)
|
|
|
3902
3900
|
initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
|
|
3903
3901
|
|
|
3904
3902
|
// build the transform matrix
|
|
3905
|
-
const sx = 2 * cameraScale / width;
|
|
3906
|
-
const sy = 2 * cameraScale / height;
|
|
3903
|
+
const sx = 2 * cameraScale / mainCanvas.width;
|
|
3904
|
+
const sy = 2 * cameraScale / mainCanvas.height;
|
|
3907
3905
|
glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), 0,
|
|
3908
3906
|
new Float32Array([
|
|
3909
3907
|
sx, 0, 0, 0,
|
|
3910
3908
|
0, sy, 0, 0,
|
|
3911
3909
|
1, 1, -1, 1,
|
|
3912
|
-
-1-sx*
|
|
3910
|
+
-1-sx*cameraPos.x, -1-sy*cameraPos.y, 0, 0
|
|
3913
3911
|
])
|
|
3914
3912
|
);
|
|
3915
3913
|
}
|
|
@@ -3957,10 +3955,10 @@ function glCopyToContext(context, forceDraw)
|
|
|
3957
3955
|
* @param uv0Y
|
|
3958
3956
|
* @param uv1X
|
|
3959
3957
|
* @param uv1Y
|
|
3960
|
-
* @param
|
|
3958
|
+
* @param rgba
|
|
3961
3959
|
* @param [rgbaAdditive=0]
|
|
3962
3960
|
* @memberof WebGL */
|
|
3963
|
-
function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba
|
|
3961
|
+
function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdditive=0)
|
|
3964
3962
|
{
|
|
3965
3963
|
// flush if there is no room for more verts or if different blend mode
|
|
3966
3964
|
if (glBatchCount == gl_MAX_BATCH || glBatchAdditive != glAdditive)
|
|
@@ -3971,43 +3969,16 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=0xffffff
|
|
|
3971
3969
|
const cx = c*sizeX, cy = c*sizeY, sx = s*sizeX, sy = s*sizeY;
|
|
3972
3970
|
|
|
3973
3971
|
// setup 2 triangles to form a quad
|
|
3974
|
-
let offset = glBatchCount++ * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT;
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
glPositionData[offset++] = y + cy - sx;
|
|
3985
|
-
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
|
|
3986
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3987
|
-
|
|
3988
|
-
// vertex 2
|
|
3989
|
-
glPositionData[offset++] = x - cx + sy;
|
|
3990
|
-
glPositionData[offset++] = y + cy + sx;
|
|
3991
|
-
glPositionData[offset++] = uv0X; glPositionData[offset++] = uv0Y;
|
|
3992
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3993
|
-
|
|
3994
|
-
// vertex 0
|
|
3995
|
-
glPositionData[offset++] = x - cx - sy;
|
|
3996
|
-
glPositionData[offset++] = y - cy + sx;
|
|
3997
|
-
glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
|
|
3998
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3999
|
-
|
|
4000
|
-
// vertex 3
|
|
4001
|
-
glPositionData[offset++] = x + cx - sy;
|
|
4002
|
-
glPositionData[offset++] = y - cy - sx;
|
|
4003
|
-
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv1Y;
|
|
4004
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
4005
|
-
|
|
4006
|
-
// vertex 1
|
|
4007
|
-
glPositionData[offset++] = x + cx + sy;
|
|
4008
|
-
glPositionData[offset++] = y + cy - sx;
|
|
4009
|
-
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
|
|
4010
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3972
|
+
for(let i=6, offset = glBatchCount++ * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT; i--;)
|
|
3973
|
+
{
|
|
3974
|
+
const a = i-4&&i>1, b = i-5&&i-2&&i-1;
|
|
3975
|
+
glPositionData[offset++] = x + (a?-cx:cx) + (b?sy:-sy);
|
|
3976
|
+
glPositionData[offset++] = y + (b?cy:-cy) + (a?sx:-sx);
|
|
3977
|
+
glPositionData[offset++] = a ? uv0X : uv1X;
|
|
3978
|
+
glPositionData[offset++] = b ? uv0Y : uv1Y;
|
|
3979
|
+
glColorData[offset++] = rgba;
|
|
3980
|
+
glColorData[offset++] = rgbaAdditive;
|
|
3981
|
+
}
|
|
4011
3982
|
}
|
|
4012
3983
|
|
|
4013
3984
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -4059,14 +4030,13 @@ function glRenderPostProcess()
|
|
|
4059
4030
|
return;
|
|
4060
4031
|
|
|
4061
4032
|
// prepare to render post process shader
|
|
4062
|
-
const width = mainCanvas.width, height = mainCanvas.height;
|
|
4063
4033
|
if (glEnable)
|
|
4064
4034
|
{
|
|
4065
4035
|
glFlush(); // clear out the buffer
|
|
4066
4036
|
mainContext.drawImage(glCanvas, 0, 0); // copy to the main canvas
|
|
4067
4037
|
}
|
|
4068
|
-
else
|
|
4069
|
-
glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height);
|
|
4038
|
+
else // set viewport
|
|
4039
|
+
glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
|
|
4070
4040
|
|
|
4071
4041
|
// setup shader program to draw one triangle
|
|
4072
4042
|
glContext.useProgram(glPostShader);
|
|
@@ -4090,7 +4060,7 @@ function glRenderPostProcess()
|
|
|
4090
4060
|
const uniformLocation = (name)=>glContext.getUniformLocation(glPostShader, name);
|
|
4091
4061
|
glContext.uniform1i(uniformLocation('iChannel0'), 0);
|
|
4092
4062
|
glContext.uniform1f(uniformLocation('iTime'), time);
|
|
4093
|
-
glContext.uniform3f(uniformLocation('iResolution'), width, height, 1);
|
|
4063
|
+
glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
|
|
4094
4064
|
glContext.drawArrays(gl_TRIANGLES, 0, 3);
|
|
4095
4065
|
}
|
|
4096
4066
|
|
|
@@ -4130,4 +4100,5 @@ gl_UNPACK_FLIP_Y_WEBGL = 37440,
|
|
|
4130
4100
|
gl_VERTICES_PER_QUAD = 6,
|
|
4131
4101
|
gl_INDICIES_PER_VERT = 6,
|
|
4132
4102
|
gl_MAX_BATCH = 1<<16,
|
|
4133
|
-
gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2
|
|
4103
|
+
gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2, // vec2 * 2 + (char * 4) * 2
|
|
4104
|
+
gl_VERTEX_BUFFER_SIZE = gl_MAX_BATCH * gl_VERTICES_PER_QUAD * gl_VERTEX_BYTE_STRIDE;
|