littlejsengine 1.4.6 → 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.
@@ -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 = ()=> (rand(2)|0) * 2 - 1;
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
- /** The seed used by the randSeeded function, should not be 0
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.x!=undefined); return new Vector2(this.x + v.x, this.y + v.y); }
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.x!=undefined); return new Vector2(this.x - v.x, this.y - v.y); }
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.x!=undefined); return new Vector2(this.x * v.x, this.y * v.y); }
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.x!=undefined); return new Vector2(this.x / v.x, this.y / v.y); }
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.x==undefined); return new Vector2(this.x * s, this.y * 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.x!=undefined); return this.x*v.x + this.y*v.y; }
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.x!=undefined); return this.x*v.y - this.y*v.x; }
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.x!=undefined); return this.add(v.subtract(this).scale(clamp(p))); }
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
 
@@ -755,7 +768,7 @@ let touchGamepadAnalog = 1;
755
768
  /** Size of virutal gamepad for touch devices in pixels
756
769
  * @default
757
770
  * @memberof Settings */
758
- let touchGamepadSize = 80;
771
+ let touchGamepadSize = 99;
759
772
 
760
773
  /** Transparency of touch gamepad overlay
761
774
  * @default
@@ -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.6';
858
+ const engineVersion = '1.4.8';
845
859
 
846
860
  /** Frames per second to update objects
847
861
  * @default */
@@ -866,18 +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
- // Engine internal variables not exposed to documentation
873
- let frameTimeLastMS = 0, frameTimeBufferMS = 0, tileImageSize, tileImageFixBleed;
874
-
875
- // Engine stat tracking, if showWatermark is true
876
- let averageFPS, drawCount;
877
-
878
- // css text used for elements created by engine
879
- const styleBody = 'margin:0;overflow:hidden;background:#000';
880
- const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
886
+ /** Set if game is paused
887
+ * @param {Boolean} paused
888
+ */
889
+ function setPaused(_paused) { paused = _paused; }
881
890
 
882
891
  ///////////////////////////////////////////////////////////////////////////////
883
892
 
@@ -899,10 +908,13 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
899
908
  debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
900
909
 
901
910
  // setup html
911
+ const styleBody = 'margin:0;overflow:hidden;background:#000' + // fill the window
912
+ ';touch-action:none' + // prevent mobile pinch to resize
913
+ ';user-select:none' + // prevent mobile hold to select
914
+ ';-webkit-user-select:none'; // compatibility for ios
902
915
  document.body.style = styleBody;
903
916
  document.body.appendChild(mainCanvas = document.createElement('canvas'));
904
917
  mainContext = mainCanvas.getContext('2d');
905
- mainCanvas.style = styleCanvas;
906
918
 
907
919
  // init stuff and start engine
908
920
  debugInit();
@@ -911,21 +923,27 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
911
923
  // create overlay canvas for hud to appear above gl canvas
912
924
  document.body.appendChild(overlayCanvas = document.createElement('canvas'));
913
925
  overlayContext = overlayCanvas.getContext('2d');
914
- overlayCanvas.style = styleCanvas;
926
+
927
+ // set canvas style to fill the window
928
+ const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
929
+ (glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;
915
930
 
916
931
  gameInit();
917
932
  touchGamepadCreate();
918
933
  engineUpdate();
919
934
  };
920
935
 
936
+ // frame time tracking
937
+ let frameTimeLastMS = 0, frameTimeBufferMS, averageFPS;
938
+
921
939
  // main update loop
922
- const engineUpdate = (frameTimeMS=0)=>
940
+ function engineUpdate(frameTimeMS=0)
923
941
  {
924
942
  // update time keeping
925
943
  let frameTimeDeltaMS = frameTimeMS - frameTimeLastMS;
926
944
  frameTimeLastMS = frameTimeMS;
927
945
  if (debug || showWatermark)
928
- averageFPS = lerp(.05, averageFPS || 0, 1e3/(frameTimeDeltaMS||1));
946
+ averageFPS = lerp(.05, averageFPS, 1e3/(frameTimeDeltaMS||1));
929
947
  const debugSpeedUp = debug && keyIsDown(107); // +
930
948
  const debugSpeedDown = debug && keyIsDown(109); // -
931
949
  if (debug)
@@ -937,24 +955,19 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
937
955
 
938
956
  if (canvasFixedSize.x)
939
957
  {
940
- // clear set fixed size
958
+ // clear canvas and set fixed size
941
959
  overlayCanvas.width = mainCanvas.width = canvasFixedSize.x;
942
960
  overlayCanvas.height = mainCanvas.height = canvasFixedSize.y;
943
961
 
944
962
  // fit to window by adding space on top or bottom if necessary
945
963
  const aspect = innerWidth / innerHeight;
946
964
  const fixedAspect = mainCanvas.width / mainCanvas.height;
947
- mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
948
- mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
949
- if (glCanvas)
950
- {
951
- glCanvas.style.width = mainCanvas.style.width;
952
- glCanvas.style.height = mainCanvas.style.height;
953
- }
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%';
954
967
  }
955
968
  else
956
969
  {
957
- // clear and set size to same as window
970
+ // clear canvas and set size to same as window
958
971
  overlayCanvas.width = mainCanvas.width = min(innerWidth, canvasMaxSize.x);
959
972
  overlayCanvas.height = mainCanvas.height = min(innerHeight, canvasMaxSize.y);
960
973
  }
@@ -1021,7 +1034,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
1021
1034
  overlayContext.fillStyle = '#000';
1022
1035
  const text = engineName + ' ' + 'v' + engineVersion + ' / '
1023
1036
  + drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
1024
- + ' ' + (glEnable ? 'GL' : '2D') ;
1037
+ + (glEnable ? ' GL' : ' 2D') ;
1025
1038
  overlayContext.fillText(text, mainCanvas.width-3, 3);
1026
1039
  overlayContext.fillStyle = '#fff';
1027
1040
  overlayContext.fillText(text, mainCanvas.width-2, 2);
@@ -1035,7 +1048,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
1035
1048
  tileImageSource ? tileImage.src = tileImageSource : tileImage.onload();
1036
1049
  }
1037
1050
 
1038
- // called by engine to setup render system
1051
+ // Called automatically by engine to setup render system
1039
1052
  function enginePreRender()
1040
1053
  {
1041
1054
  // save canvas size
@@ -1045,12 +1058,10 @@ function enginePreRender()
1045
1058
  mainContext.imageSmoothingEnabled = !cavasPixelated;
1046
1059
 
1047
1060
  // setup gl rendering if enabled
1048
- glEnable && glPreRender(mainCanvas.width, mainCanvas.height, cameraPos.x, cameraPos.y, cameraScale);
1061
+ glEnable && glPreRender();
1049
1062
  }
1050
1063
 
1051
- ///////////////////////////////////////////////////////////////////////////////
1052
-
1053
- /** 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 */
1054
1065
  function engineObjectsUpdate()
1055
1066
  {
1056
1067
  // get list of solid objects for physics optimzation
@@ -1153,7 +1164,7 @@ class EngineObject
1153
1164
  constructor(pos=vec2(), size=objectDefaultSize, tileIndex=-1, tileSize=tileSizeDefault, angle=0, color, renderOrder=0)
1154
1165
  {
1155
1166
  // set passed in params
1156
- ASSERT(pos && pos.x != undefined && size.x != undefined); // ensure pos and size are vec2s
1167
+ ASSERT(isVector2(pos) && isVector2(size)); // ensure pos and size are vec2s
1157
1168
 
1158
1169
  /** @property {Vector2} - World space position of the object */
1159
1170
  this.pos = pos.copy();
@@ -1243,7 +1254,7 @@ class EngineObject
1243
1254
  if (this.collideSolidObjects)
1244
1255
  {
1245
1256
  // check collisions against solid objects
1246
- const epsilon = 1e-3; // necessary to push slightly outside of the collision
1257
+ const epsilon = .001; // necessary to push slightly outside of the collision
1247
1258
  for (const o of engineObjectsCollide)
1248
1259
  {
1249
1260
  // non solid objects don't collide with eachother
@@ -1506,11 +1517,6 @@ class EngineObject
1506
1517
 
1507
1518
  'use strict';
1508
1519
 
1509
- /** Tile sheet for batch rendering system
1510
- * @type {Image}
1511
- * @memberof Draw */
1512
- const tileImage = new Image();
1513
-
1514
1520
  /** The primary 2D canvas visible to the user
1515
1521
  * @type {HTMLCanvasElement}
1516
1522
  * @memberof Draw */
@@ -1536,6 +1542,14 @@ let overlayContext;
1536
1542
  * @memberof Draw */
1537
1543
  let mainCanvasSize = vec2();
1538
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
+
1539
1553
  /** Convert from screen to world space coordinates
1540
1554
  * - if calling outside of render, you may need to manually set mainCanvasSize
1541
1555
  * @param {Vector2} screenPos
@@ -1560,7 +1574,7 @@ const worldToScreen = (worldPos)=>
1560
1574
 
1561
1575
  /** Draw textured tile centered in world space, with color applied if using WebGL
1562
1576
  * @param {Vector2} pos - Center of the tile in world space
1563
- * @param {Vector2} [size=new Vector2(1,1)] - Size of the tile in world space, width and height
1577
+ * @param {Vector2} [size=new Vector2(1,1)] - Size of the tile in world space
1564
1578
  * @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
1565
1579
  * @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
1566
1580
  * @param {Color} [color=new Color(1,1,1)] - Color to modulate with
@@ -1779,10 +1793,10 @@ class FontImage
1779
1793
  */
1780
1794
  constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), startTileIndex=0, context=overlayContext)
1781
1795
  {
1782
- if (!image && !engineFontImage)
1796
+ if (!engineFontImage)
1783
1797
  {
1784
1798
  // load default font image
1785
- engineFontImage = new Image();
1799
+ engineFontImage = new Image;
1786
1800
  engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
1787
1801
  }
1788
1802
 
@@ -1849,7 +1863,7 @@ class FontImage
1849
1863
  /** Returns true if fullscreen mode is active
1850
1864
  * @return {Boolean}
1851
1865
  * @memberof Draw */
1852
- const isFullscreen =()=> document.fullscreenElement;
1866
+ const isFullscreen = ()=> document.fullscreenElement;
1853
1867
 
1854
1868
  /** Toggle fullsceen mode
1855
1869
  * @memberof Draw */
@@ -1859,16 +1873,9 @@ function toggleFullscreen()
1859
1873
  {
1860
1874
  if (document.exitFullscreen)
1861
1875
  document.exitFullscreen();
1862
- else if (document.mozCancelFullScreen)
1863
- document.mozCancelFullScreen();
1864
- }
1865
- else
1866
- {
1867
- if (document.body.webkitRequestFullScreen)
1868
- document.body.webkitRequestFullScreen();
1869
- else if (document.body.mozRequestFullScreen)
1870
- document.body.mozRequestFullScreen();
1871
1876
  }
1877
+ else if (document.body.requestFullscreen)
1878
+ document.body.requestFullscreen();
1872
1879
  }
1873
1880
 
1874
1881
  /**
@@ -1887,7 +1894,7 @@ function toggleFullscreen()
1887
1894
  * @param {Number} [device=0]
1888
1895
  * @return {Boolean}
1889
1896
  * @memberof Input */
1890
- const keyIsDown = (key, device=0)=> inputData[device] && inputData[device][key] & 1 ? 1 : 0;
1897
+ const keyIsDown = (key, device=0)=> inputData[device] && inputData[device][key] & 1;
1891
1898
 
1892
1899
  /** Returns true if device key was pressed this frame
1893
1900
  * @param {Number} key
@@ -2126,32 +2133,41 @@ const isTouchDevice = window.ontouchstart !== undefined;
2126
2133
  // try to enable touch mouse
2127
2134
  if (isTouchDevice)
2128
2135
  {
2129
- // handle all touch events the same way
2130
- let wasTouching, hadTouch;
2131
- ontouchstart = ontouchmove = ontouchend = (e)=>
2136
+ // override mouse events
2137
+ let wasTouching, mouseDown = onmousedown, mouseUp = onmouseup;
2138
+ onmousedown = onmouseup = ()=> 0;
2139
+
2140
+ // setup touch input
2141
+ ontouchstart = (e)=>
2132
2142
  {
2133
- e.button = 0; // all touches are left click
2143
+ // fix mobile audio, force it to play a sound on first touch
2144
+ zzfx(0);
2134
2145
 
2135
- // check if touching and pass to mouse events
2136
- const touching = e.touches.length;
2137
- if (touching)
2146
+ // handle all touch events the same way
2147
+ ontouchstart = ontouchmove = ontouchend = (e)=>
2138
2148
  {
2139
- // fix mobile audio, force it to play a sound on first touch
2140
- hadTouch || zzfx(0, hadTouch=1);
2149
+ e.button = 0; // all touches are left click
2141
2150
 
2142
- // set event pos and pass it along
2143
- e.x = e.touches[0].clientX;
2144
- e.y = e.touches[0].clientY;
2145
- wasTouching ? onmousemove(e) : onmousedown(e);
2146
- }
2147
- else if (wasTouching)
2148
- onmouseup(e);
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);
2149
2162
 
2150
- // set was touching
2151
- wasTouching = touching;
2163
+ // set was touching
2164
+ wasTouching = touching;
2152
2165
 
2153
- // must return true so the document will get focus
2154
- return true;
2166
+ // must return true so the document will get focus
2167
+ return true;
2168
+ }
2169
+
2170
+ return ontouchstart(e);
2155
2171
  }
2156
2172
  }
2157
2173
 
@@ -2159,7 +2175,7 @@ if (isTouchDevice)
2159
2175
  // touch gamepad, virtual on screen gamepad emulator for touch devices
2160
2176
 
2161
2177
  // touch input internal variables
2162
- let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick = vec2();
2178
+ let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
2163
2179
 
2164
2180
  // create the touch gamepad, called automatically by the engine
2165
2181
  function touchGamepadCreate()
@@ -2167,65 +2183,73 @@ function touchGamepadCreate()
2167
2183
  if (!touchGamepadEnable || !isTouchDevice)
2168
2184
  return;
2169
2185
 
2170
- ontouchstart = ontouchmove = ontouchend = (e)=>
2186
+ // touch input internal variables
2187
+ touchGamepadButtons = [];
2188
+ touchGamepadStick = vec2();
2189
+
2190
+ // setup touch input
2191
+ ontouchstart = (e)=>
2171
2192
  {
2172
- if (!touchGamepadEnable)
2173
- return;
2193
+ // fix mobile audio, force it to play a sound on first touch
2194
+ zzfx(0);
2174
2195
 
2175
- // clear touch gamepad input
2176
- touchGamepadStick = vec2();
2177
- touchGamepadButtons = [];
2178
-
2179
- const touching = e.touches.length;
2180
- if (touching)
2196
+ ontouchstart = ontouchmove = ontouchend = (e)=>
2181
2197
  {
2182
- touchGamepadTimer.isSet() || zzfx(0) ; // fix mobile audio, force it to play a sound the first time
2183
-
2184
- // set that gamepad is active
2185
- isUsingGamepad = 1;
2186
- touchGamepadTimer.set();
2187
-
2188
- if (paused)
2198
+ // clear touch gamepad input
2199
+ touchGamepadStick = vec2();
2200
+ touchGamepadButtons = [];
2201
+
2202
+ const touching = e.touches.length;
2203
+ if (touching)
2189
2204
  {
2190
- // touch anywhere to press start when paused
2191
- touchGamepadButtons[9] = 1;
2192
- return;
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
+ }
2193
2215
  }
2194
- }
2195
2216
 
2196
- // get center of left and right sides
2197
- const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
2198
- const buttonCenter = mainCanvasSize.subtract(vec2(touchGamepadSize, touchGamepadSize));
2199
- const startCenter = mainCanvasSize.scale(.5);
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);
2200
2221
 
2201
- // check each touch point
2202
- for (const touch of e.touches)
2203
- {
2204
- const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
2205
- if (touchPos.distance(stickCenter) < touchGamepadSize)
2222
+ // check each touch point
2223
+ for (const touch of e.touches)
2206
2224
  {
2207
- // virtual analog stick
2208
- if (touchGamepadAnalog)
2209
- touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
2210
- else
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)
2211
2239
  {
2212
- // 8 way dpad
2213
- const angle = touchPos.subtract(stickCenter).angle();
2214
- touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
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;
2215
2248
  }
2216
- }
2217
- else if (touchPos.distance(buttonCenter) < touchGamepadSize)
2218
- {
2219
- // virtual face buttons
2220
- const button = touchPos.subtract(buttonCenter).direction();
2221
- touchGamepadButtons[button] = 1;
2222
- }
2223
- else if (touchPos.distance(startCenter) < touchGamepadSize)
2224
- {
2225
- // virtual start button in center
2226
- touchGamepadButtons[9] = 1;
2227
2249
  }
2228
2250
  }
2251
+
2252
+ return ontouchstart(e);
2229
2253
  }
2230
2254
  }
2231
2255
 
@@ -2343,7 +2367,7 @@ class Sound
2343
2367
  {
2344
2368
  if (!soundEnable) return;
2345
2369
 
2346
- let pan = 0;
2370
+ let pan;
2347
2371
  if (pos)
2348
2372
  {
2349
2373
  const range = this.range;
@@ -2373,7 +2397,7 @@ class Sound
2373
2397
  * @param {Number} [volume=1] - How much to scale volume by (in addition to range fade)
2374
2398
  * @return {AudioBufferSourceNode} - The audio, can be used to stop sound later
2375
2399
  */
2376
- playNote(semitoneOffset, pos, volume=1)
2400
+ playNote(semitoneOffset, pos, volume)
2377
2401
  {
2378
2402
  if (!soundEnable) return;
2379
2403
 
@@ -2428,7 +2452,7 @@ class Music
2428
2452
  * @param {Boolean} [loop=1] - True if the music should loop when it reaches the end
2429
2453
  * @return {AudioBufferSourceNode} - The audio node, can be used to stop sound later
2430
2454
  */
2431
- play(volume = 1, loop = 1)
2455
+ play(volume, loop = 1)
2432
2456
  {
2433
2457
  if (!soundEnable) return;
2434
2458
 
@@ -2510,7 +2534,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
2510
2534
 
2511
2535
  // create audio context
2512
2536
  if (!audioContext)
2513
- audioContext = new (window.AudioContext||webkitAudioContext);
2537
+ audioContext = new AudioContext;
2514
2538
 
2515
2539
  // fix stalled audio
2516
2540
  audioContext.resume();
@@ -2529,18 +2553,13 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
2529
2553
  source.playbackRate.value = rate;
2530
2554
  source.loop = loop;
2531
2555
 
2532
- // create and connect gain node (createGain is more widley spported then GainNode construtor)
2556
+ // create and connect gain node (createGain is more widely spported then GainNode construtor)
2533
2557
  const gainNode = audioContext.createGain();
2534
2558
  gainNode.gain.value = soundVolume*volume;
2535
2559
  gainNode.connect(audioContext.destination);
2536
2560
 
2537
- // connect source to gain
2538
- (
2539
- window.StereoPannerNode ? // create pan node if possible
2540
- source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)}))
2541
- : source
2542
- )
2543
- .connect(gainNode);
2561
+ // connect source to stereo panner and gain
2562
+ source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)})).connect(gainNode);
2544
2563
 
2545
2564
  // play and return sound
2546
2565
  source.start();
@@ -2658,10 +2677,8 @@ function zzfxG
2658
2677
  * @memberof Audio */
2659
2678
  function zzfxM(instruments, patterns, sequence, BPM = 125)
2660
2679
  {
2680
+ let i, j, k;
2661
2681
  let instrumentParameters;
2662
- let i;
2663
- let j;
2664
- let k;
2665
2682
  let note;
2666
2683
  let sample;
2667
2684
  let patternChannel;
@@ -2831,14 +2848,11 @@ function tileCollisionRaycast(posStart, posEnd, object)
2831
2848
  {
2832
2849
  // test if a ray collides with tiles from start to end
2833
2850
  // todo: a way to get the exact hit point, it must still register as inside the hit tile
2834
- posStart = posStart.floor();
2835
- posEnd = posEnd.floor();
2836
- const posDelta = posEnd.subtract(posStart);
2851
+ const posDelta = (posEnd = posEnd.floor()).subtract(posStart = posStart.floor());
2837
2852
  const dx = abs(posDelta.x), dy = -abs(posDelta.y);
2838
2853
  const sx = sign(posDelta.x), sy = sign(posDelta.y);
2839
- let e = dx + dy;
2840
2854
 
2841
- for (let x = posStart.x, y = posStart.y;;)
2855
+ for (let x = posStart.x, y = posStart.y, e = dx + dy;;)
2842
2856
  {
2843
2857
  const tileData = getTileCollisionData(vec2(x,y));
2844
2858
  if (tileData && (object ? object.collideWithTileRaycast(tileData, new Vector2(x, y)) : tileData > 0))
@@ -2930,7 +2944,7 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
2930
2944
  // init tile data
2931
2945
  this.data = [];
2932
2946
  for (let j = this.size.area(); j--;)
2933
- this.data.push(new TileLayerData());
2947
+ this.data.push(new TileLayerData);
2934
2948
  }
2935
2949
 
2936
2950
  /** Set data at a given position in the array
@@ -2986,21 +3000,23 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
2986
3000
  * @param {Boolean} [clear=0] - Should it clear the canvas before drawing */
2987
3001
  redrawStart(clear = 0)
2988
3002
  {
2989
- if (clear)
2990
- {
2991
- // clear and set size
2992
- this.canvas.width = this.size.x * this.tileSize.x;
2993
- this.canvas.height = this.size.y * this.tileSize.y;
2994
- }
2995
-
2996
3003
  // save current render settings
2997
- this.savedRenderSettings = [mainCanvas, mainContext, cameraPos, cameraScale];
3004
+ this.savedRenderSettings = [mainCanvas, mainContext, mainCanvasSize, cameraPos, cameraScale];
2998
3005
 
2999
- // use normal rendering system to render the tiles
3006
+ // hack: use normal rendering system to render the tiles
3000
3007
  mainCanvas = this.canvas;
3001
3008
  mainContext = this.context;
3002
3009
  cameraPos = this.size.scale(.5);
3003
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
3004
3020
  enginePreRender();
3005
3021
  }
3006
3022
 
@@ -3012,7 +3028,7 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3012
3028
  //debugSaveCanvas(this.canvas);
3013
3029
 
3014
3030
  // set stuff back to normal
3015
- [mainCanvas, mainContext, cameraPos, cameraScale] = this.savedRenderSettings;
3031
+ [mainCanvas, mainContext, mainCanvasSize, cameraPos, cameraScale] = this.savedRenderSettings;
3016
3032
  }
3017
3033
 
3018
3034
  /** Draw the tile at a given position
@@ -3253,7 +3269,7 @@ class ParticleEmitter extends EngineObject
3253
3269
  this.parent && super.update();
3254
3270
 
3255
3271
  // update emitter
3256
- if (!this.emitTime || this.getAliveTime() <= this.emitTime)
3272
+ if (!this.emitTime | this.getAliveTime() <= this.emitTime)
3257
3273
  {
3258
3274
  // emit particles
3259
3275
  if (this.emitRate * particleEmitRateScale)
@@ -3275,9 +3291,9 @@ class ParticleEmitter extends EngineObject
3275
3291
  {
3276
3292
  // spawn a particle
3277
3293
  let pos = this.emitSize.x != undefined ? // check if vec2 was used for size
3278
- (new Vector2(rand(-.5,.5), rand(-.5,.5)))
3279
- .multiply(this.emitSize).rotate(this.angle) // box emitter
3280
- : randInCircle(this.emitSize * .5); // circle emitter
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
3281
3297
  let angle = rand(this.particleConeAngle, -this.particleConeAngle);
3282
3298
  if (!this.localSpace)
3283
3299
  {
@@ -3320,7 +3336,7 @@ class ParticleEmitter extends EngineObject
3320
3336
  particle.additive = this.additive;
3321
3337
  particle.renderOrder = this.renderOrder;
3322
3338
  particle.trailScale = this.trailScale;
3323
- particle.mirror = rand()<.5;
3339
+ particle.mirror = randInt(2);
3324
3340
  particle.localSpaceEmitter = this.localSpace && this;
3325
3341
 
3326
3342
  // setup callbacks for particles
@@ -3349,7 +3365,8 @@ class Particle extends EngineObject
3349
3365
  * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
3350
3366
  * @param {Number} [angle=0] - Angle to rotate the particle
3351
3367
  */
3352
- constructor(pos, tileIndex, tileSize, angle) { super(pos, new Vector2, tileIndex, tileSize, angle); }
3368
+ constructor(pos, tileIndex, tileSize, angle)
3369
+ { super(pos, new Vector2, tileIndex, tileSize, angle); }
3353
3370
 
3354
3371
  /** Render the particle, automatically called each frame, sorted by renderOrder */
3355
3372
  render()
@@ -3469,7 +3486,7 @@ class Medal
3469
3486
  this.name = name;
3470
3487
  this.description = description;
3471
3488
  this.icon = icon;
3472
- this.image = new Image();
3489
+ this.image = new Image;
3473
3490
  if (src)
3474
3491
  this.image.src = src;
3475
3492
  }
@@ -3484,10 +3501,7 @@ class Medal
3484
3501
  ASSERT(medalsSaveName); // save name must be set
3485
3502
  localStorage[this.storageKey()] = this.unlocked = 1;
3486
3503
  medalsDisplayQueue.push(this);
3487
-
3488
- // save for newgrounds and OS13K
3489
3504
  newgrounds && newgrounds.unlockMedal(this.id);
3490
- localStorage['OS13kTrophy,' + this.icon + ',' + medalsSaveName + ',' + this.name] = this.description;
3491
3505
  }
3492
3506
 
3493
3507
  /** Render a medal
@@ -3503,20 +3517,19 @@ class Medal
3503
3517
  // draw containing rect and clip to that region
3504
3518
  context.save();
3505
3519
  context.beginPath();
3506
- context.fillStyle = '#ddd'
3507
- context.fill(context.rect(x, y, width, medalDisplayHeight));
3508
- context.strokeStyle = '#000';
3520
+ context.fillStyle = new Color(.9,.9,.9);
3521
+ context.strokeStyle = new Color(0,0,0);
3509
3522
  context.lineWidth = 3;
3523
+ context.fill(context.rect(x, y, width, medalDisplayHeight));
3510
3524
  context.stroke();
3511
3525
  context.clip();
3512
3526
 
3513
3527
  // draw the icon and text
3514
- this.renderIcon(x+15+medalDisplayIconSize/2, y+medalDisplayHeight/2);
3515
- context.textAlign = 'left';
3516
- context.font = '38px '+ fontDefault;
3517
- context.fillText(this.name, x+medalDisplayIconSize+30, y+28);
3518
- context.font = '24px '+ fontDefault;
3519
- 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');
3520
3533
  context.restore();
3521
3534
  }
3522
3535
 
@@ -3525,18 +3538,13 @@ class Medal
3525
3538
  * @param {Number} y - Screen space Y position
3526
3539
  * @param {Number} [size=medalDisplayIconSize] - Screen space size
3527
3540
  */
3528
- renderIcon(x, y, size=medalDisplayIconSize)
3541
+ renderIcon(pos, size=medalDisplayIconSize)
3529
3542
  {
3530
3543
  // draw the image or icon
3531
- const context = overlayContext;
3532
- context.fillStyle = '#000';
3533
- context.textAlign = 'center';
3534
- context.textBaseline = 'middle';
3535
- context.font = size*.7 + 'px '+ fontDefault;
3536
- if (this.image.src)
3537
- 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);
3538
3546
  else
3539
- context.fillText(this.icon, x, y); // show icon if there is no image
3547
+ drawTextScreen(this.icon, pos, size*.7, new Color(0,0,0));
3540
3548
  }
3541
3549
 
3542
3550
  // Get local storage key used by the medal
@@ -3594,13 +3602,21 @@ class Newgrounds
3594
3602
  constructor(app_id, cipher)
3595
3603
  {
3596
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
+
3597
3616
  this.app_id = app_id;
3598
3617
  this.cipher = cipher;
3599
3618
  this.host = location ? location.hostname : '';
3600
3619
 
3601
- // create an instance of CryptoJS for encrypted calls
3602
- cipher && (this.cryptoJS = CryptoJS());
3603
-
3604
3620
  // get session id from url search params
3605
3621
  const url = new URL(location.href);
3606
3622
  this.session_id = url.searchParams.get('ngio_session_id') || 0;
@@ -3703,12 +3719,6 @@ class Newgrounds
3703
3719
  return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
3704
3720
  }
3705
3721
  }
3706
-
3707
- ///////////////////////////////////////////////////////////////////////////////
3708
- // Crypto-JS - https://github.com/brix/crypto-js [The MIT License (MIT)]
3709
- // Copyright (c) 2009-2013 Jeff Mott Copyright (c) 2013-2016 Evan Vosberg
3710
-
3711
- 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));
3712
3722
  /**
3713
3723
  * LittleJS WebGL Interface
3714
3724
  * <br> - All webgl used by the engine is wrapped up here
@@ -3738,7 +3748,7 @@ let glContext;
3738
3748
  let glTileTexture;
3739
3749
 
3740
3750
  // WebGL internal variables not exposed to documentation
3741
- let glActiveTexture, glShader, glArrayBuffer, glVertexData, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
3751
+ let glActiveTexture, glShader, glArrayBuffer, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
3742
3752
 
3743
3753
  ///////////////////////////////////////////////////////////////////////////////
3744
3754
 
@@ -3748,7 +3758,6 @@ function glInit()
3748
3758
  // create the canvas and tile texture
3749
3759
  glCanvas = document.createElement('canvas');
3750
3760
  glContext = glCanvas.getContext('webgl', {antialias: false});
3751
- glCanvas.style = styleCanvas;
3752
3761
  glTileTexture = glCreateTexture(tileImage);
3753
3762
 
3754
3763
  // some browsers are much faster without copying the gl buffer so we just overlay it instead
@@ -3760,27 +3769,25 @@ function glInit()
3760
3769
  'uniform mat4 m;'+ // transform matrix
3761
3770
  'attribute vec2 p,t;'+ // position, uv
3762
3771
  'attribute vec4 c,a;'+ // color, additiveColor
3763
- 'varying vec2 v;'+ // return uv
3764
- 'varying vec4 d,e;'+ // return color, additiveColor
3772
+ 'varying vec4 v,d,e;'+ // return uv, color, additiveColor
3765
3773
  'void main(){'+ // shader entry point
3766
3774
  'gl_Position=m*vec4(p,1,1);'+ // transform position
3767
- 'v=t;d=c;e=a;'+ // pass stuff to fragment shader
3775
+ 'v=vec4(t,p);d=c;e=a;'+ // pass stuff to fragment shader
3768
3776
  '}' // end of shader
3769
3777
  ,
3770
- 'precision highp float;'+ // use highp for better accuracy
3771
- 'varying vec2 v;'+ // uv
3772
- 'varying vec4 d,e;'+ // color, additiveColor
3773
- 'uniform sampler2D s;'+ // texture
3774
- 'void main(){'+ // shader entry point
3775
- 'gl_FragColor=texture2D(s,v)*d+e;'+ // modulate texture by color plus additive
3776
- '}' // 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
3777
3784
  );
3778
3785
 
3779
3786
  // init buffers
3780
- glVertexData = new ArrayBuffer(gl_MAX_BATCH * gl_VERTICES_PER_QUAD * gl_VERTEX_BYTE_STRIDE);
3787
+ const vertexData = new ArrayBuffer(gl_VERTEX_BUFFER_SIZE);
3781
3788
  glArrayBuffer = glContext.createBuffer();
3782
- glPositionData = new Float32Array(glVertexData);
3783
- glColorData = new Uint32Array(glVertexData);
3789
+ glPositionData = new Float32Array(vertexData);
3790
+ glColorData = new Uint32Array(vertexData);
3784
3791
  glBatchCount = 0;
3785
3792
  }
3786
3793
 
@@ -3855,18 +3862,19 @@ function glCreateTexture(image)
3855
3862
  image && image.width && glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
3856
3863
 
3857
3864
  // use point filtering for pixelated rendering
3858
- glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, cavasPixelated ? gl_NEAREST : gl_LINEAR);
3859
- glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, cavasPixelated ? gl_NEAREST : gl_LINEAR);
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);
3860
3868
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE);
3861
3869
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE);
3862
3870
  return texture;
3863
3871
  }
3864
3872
 
3865
3873
  // called automatically by engine before render
3866
- function glPreRender(width, height, cameraX, cameraY, cameraScale)
3874
+ function glPreRender()
3867
3875
  {
3868
3876
  // clear and set to same size as main canvas
3869
- glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height);
3877
+ glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
3870
3878
  glContext.clear(gl_COLOR_BUFFER_BIT);
3871
3879
 
3872
3880
  // set up the shader
@@ -3874,7 +3882,7 @@ function glPreRender(width, height, cameraX, cameraY, cameraScale)
3874
3882
  glContext.activeTexture(gl_TEXTURE0);
3875
3883
  glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
3876
3884
  glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
3877
- glContext.bufferData(gl_ARRAY_BUFFER, glVertexData.byteLength, gl_DYNAMIC_DRAW);
3885
+ glContext.bufferData(gl_ARRAY_BUFFER, gl_VERTEX_BUFFER_SIZE, gl_DYNAMIC_DRAW);
3878
3886
  glSetBlendMode();
3879
3887
 
3880
3888
  // set vertex attributes
@@ -3892,14 +3900,14 @@ function glPreRender(width, height, cameraX, cameraY, cameraScale)
3892
3900
  initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
3893
3901
 
3894
3902
  // build the transform matrix
3895
- const sx = 2 * cameraScale / width;
3896
- const sy = 2 * cameraScale / height;
3903
+ const sx = 2 * cameraScale / mainCanvas.width;
3904
+ const sy = 2 * cameraScale / mainCanvas.height;
3897
3905
  glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), 0,
3898
3906
  new Float32Array([
3899
3907
  sx, 0, 0, 0,
3900
3908
  0, sy, 0, 0,
3901
3909
  1, 1, -1, 1,
3902
- -1-sx*cameraX, -1-sy*cameraY, 0, 0
3910
+ -1-sx*cameraPos.x, -1-sy*cameraPos.y, 0, 0
3903
3911
  ])
3904
3912
  );
3905
3913
  }
@@ -3947,10 +3955,10 @@ function glCopyToContext(context, forceDraw)
3947
3955
  * @param uv0Y
3948
3956
  * @param uv1X
3949
3957
  * @param uv1Y
3950
- * @param [rgba=0xffffffff]
3958
+ * @param rgba
3951
3959
  * @param [rgbaAdditive=0]
3952
3960
  * @memberof WebGL */
3953
- function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=0xffffffff, rgbaAdditive=0)
3961
+ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdditive=0)
3954
3962
  {
3955
3963
  // flush if there is no room for more verts or if different blend mode
3956
3964
  if (glBatchCount == gl_MAX_BATCH || glBatchAdditive != glAdditive)
@@ -3961,43 +3969,16 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=0xffffff
3961
3969
  const cx = c*sizeX, cy = c*sizeY, sx = s*sizeX, sy = s*sizeY;
3962
3970
 
3963
3971
  // setup 2 triangles to form a quad
3964
- let offset = glBatchCount++ * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT;
3965
-
3966
- // vertex 0
3967
- glPositionData[offset++] = x - cx - sy;
3968
- glPositionData[offset++] = y - cy + sx;
3969
- glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
3970
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3971
-
3972
- // vertex 1
3973
- glPositionData[offset++] = x + cx + sy;
3974
- glPositionData[offset++] = y + cy - sx;
3975
- glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
3976
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3977
-
3978
- // vertex 2
3979
- glPositionData[offset++] = x - cx + sy;
3980
- glPositionData[offset++] = y + cy + sx;
3981
- glPositionData[offset++] = uv0X; glPositionData[offset++] = uv0Y;
3982
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3983
-
3984
- // vertex 0
3985
- glPositionData[offset++] = x - cx - sy;
3986
- glPositionData[offset++] = y - cy + sx;
3987
- glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
3988
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3989
-
3990
- // vertex 3
3991
- glPositionData[offset++] = x + cx - sy;
3992
- glPositionData[offset++] = y - cy - sx;
3993
- glPositionData[offset++] = uv1X; glPositionData[offset++] = uv1Y;
3994
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3995
-
3996
- // vertex 1
3997
- glPositionData[offset++] = x + cx + sy;
3998
- glPositionData[offset++] = y + cy - sx;
3999
- glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
4000
- 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
+ }
4001
3982
  }
4002
3983
 
4003
3984
  ///////////////////////////////////////////////////////////////////////////////
@@ -4049,14 +4030,13 @@ function glRenderPostProcess()
4049
4030
  return;
4050
4031
 
4051
4032
  // prepare to render post process shader
4052
- const width = mainCanvas.width, height = mainCanvas.height;
4053
4033
  if (glEnable)
4054
4034
  {
4055
4035
  glFlush(); // clear out the buffer
4056
4036
  mainContext.drawImage(glCanvas, 0, 0); // copy to the main canvas
4057
4037
  }
4058
- else
4059
- glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height); // set viewport
4038
+ else // set viewport
4039
+ glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
4060
4040
 
4061
4041
  // setup shader program to draw one triangle
4062
4042
  glContext.useProgram(glPostShader);
@@ -4080,7 +4060,7 @@ function glRenderPostProcess()
4080
4060
  const uniformLocation = (name)=>glContext.getUniformLocation(glPostShader, name);
4081
4061
  glContext.uniform1i(uniformLocation('iChannel0'), 0);
4082
4062
  glContext.uniform1f(uniformLocation('iTime'), time);
4083
- glContext.uniform3f(uniformLocation('iResolution'), width, height, 1);
4063
+ glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
4084
4064
  glContext.drawArrays(gl_TRIANGLES, 0, 3);
4085
4065
  }
4086
4066
 
@@ -4120,4 +4100,5 @@ gl_UNPACK_FLIP_Y_WEBGL = 37440,
4120
4100
  gl_VERTICES_PER_QUAD = 6,
4121
4101
  gl_INDICIES_PER_VERT = 6,
4122
4102
  gl_MAX_BATCH = 1<<16,
4123
- gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2; // vec2 * 2 + (char * 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;