littlejsengine 1.13.1 → 1.13.4

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.
Files changed (66) hide show
  1. package/README.md +15 -12
  2. package/dist/littlejs.d.ts +94 -74
  3. package/dist/littlejs.esm.js +422 -306
  4. package/dist/littlejs.esm.min.js +1 -1
  5. package/dist/littlejs.js +416 -306
  6. package/dist/littlejs.min.js +1 -1
  7. package/dist/littlejs.release.js +414 -304
  8. package/examples/box2d/game.js +1 -1
  9. package/examples/box2d/gameObjects.js +1 -1
  10. package/examples/box2d/scenes.js +1 -1
  11. package/examples/breakoutTutorial/README.md +44 -41
  12. package/examples/breakoutTutorial/game.js +2 -2
  13. package/examples/electron/build.bat +7 -0
  14. package/examples/electron/build.js +124 -0
  15. package/examples/electron/electron.js +35 -0
  16. package/examples/electron/game.js +140 -0
  17. package/examples/electron/index.html +13 -0
  18. package/examples/electron/package.json +12 -0
  19. package/examples/electron/tiles.png +0 -0
  20. package/examples/empty/game.js +1 -0
  21. package/examples/htmlMenu/game.js +1 -1
  22. package/examples/index.html +154 -93
  23. package/examples/module/game.js +5 -2
  24. package/examples/particles/index.html +12 -3
  25. package/examples/platformer/gameEffects.js +3 -3
  26. package/examples/shorts/base.html +1 -1
  27. package/examples/shorts/blending.js +9 -5
  28. package/examples/shorts/box2d.js +1 -1
  29. package/examples/shorts/box2dCar.js +1 -1
  30. package/examples/shorts/clock.js +1 -1
  31. package/examples/shorts/colors.js +2 -2
  32. package/examples/shorts/maze.js +1 -1
  33. package/examples/shorts/nineSlice.js +9 -9
  34. package/examples/shorts/piano.js +2 -2
  35. package/examples/shorts/raycasting.js +2 -2
  36. package/examples/shorts/shapes.js +9 -9
  37. package/examples/shorts/slidingPuzzle.js +2 -2
  38. package/examples/shorts/starfield.js +5 -7
  39. package/examples/shorts/systemFont.js +1 -1
  40. package/examples/shorts/uiSystem.js +1 -0
  41. package/examples/starter/build.js +9 -8
  42. package/examples/starter/game.js +5 -2
  43. package/examples/starter/index.html +2 -2
  44. package/examples/stress/index.html +5 -5
  45. package/examples/style.css +5 -2
  46. package/examples/typescript/build.js +1 -1
  47. package/examples/typescript/game.js +2 -2
  48. package/examples/typescript/game.ts +5 -2
  49. package/examples/uiSystem/game.js +2 -1
  50. package/package.json +2 -2
  51. package/plugins/box2d.js +19 -94
  52. package/plugins/drawUtilities.js +24 -18
  53. package/plugins/postProcess.js +7 -0
  54. package/plugins/uiSystem.js +4 -4
  55. package/src/engine.js +7 -2
  56. package/src/engineAudio.js +1 -1
  57. package/src/engineDebug.js +2 -2
  58. package/src/engineDraw.js +243 -150
  59. package/src/engineExport.js +6 -0
  60. package/src/engineInput.js +2 -2
  61. package/src/engineMedals.js +4 -4
  62. package/src/engineObject.js +3 -3
  63. package/src/engineParticles.js +8 -1
  64. package/src/engineSettings.js +55 -8
  65. package/src/engineUtilities.js +7 -7
  66. package/src/engineWebGL.js +25 -5
@@ -33,7 +33,7 @@ const engineName = 'LittleJS';
33
33
  * @type {string}
34
34
  * @default
35
35
  * @memberof Engine */
36
- const engineVersion = '1.13.1';
36
+ const engineVersion = '1.13.4';
37
37
 
38
38
  /** Frames per second to update
39
39
  * @type {number}
@@ -300,7 +300,6 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
300
300
  'margin:0;' + // fill the window
301
301
  'overflow:hidden;' + // no scroll bars
302
302
  'background:#000;' + // set background color
303
- (canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
304
303
  'user-select:none;' + // prevent hold to select
305
304
  '-webkit-user-select:none;' + // compatibility for ios
306
305
  (!touchInputEnable ? '' : // no touch css settings
@@ -328,7 +327,13 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
328
327
  mainCanvas.style.cssText = overlayCanvas.style.cssText = styleCanvas;
329
328
  if (glCanvas)
330
329
  glCanvas.style.cssText = styleCanvas;
330
+ setCanvasPixelated(canvasPixelated);
331
+ setOverlayCanvasPixelated(overlayCanvasPixelated);
331
332
  updateCanvas();
333
+
334
+ // create offscreen canvas for image processing
335
+ workCanvas = new OffscreenCanvas(256, 256);
336
+ workContext = workCanvas.getContext('2d', { willReadFrequently: true });
332
337
 
333
338
  // create promises for loading images
334
339
  const promises = imageSources.map((src, textureIndex)=>
@@ -817,11 +822,12 @@ function isPowerOfTwo(value) { return !(value & (value - 1)); }
817
822
  * @memberof Utilities */
818
823
  function nearestPowerOfTwo(value) { return 2**Math.ceil(Math.log2(value)); }
819
824
 
820
- /** Returns true if two axis aligned bounding boxes are overlapping
825
+ /** Returns true if two axis aligned bounding boxes are overlapping
826
+ * this can be used for simple collision detection between objects
821
827
  * @param {Vector2} posA - Center of box A
822
828
  * @param {Vector2} sizeA - Size of box A
823
829
  * @param {Vector2} posB - Center of box B
824
- * @param {Vector2} [sizeB=(0,0)] - Size of box B, a point if undefined
830
+ * @param {Vector2} [sizeB=(0,0)] - Size of box B, uses a point if undefined
825
831
  * @return {boolean} - True if overlapping
826
832
  * @memberof Utilities */
827
833
  function isOverlapping(posA, sizeA, posB, sizeB=vec2())
@@ -875,10 +881,11 @@ function isIntersecting(start, end, pos, size)
875
881
  * @param {number} [frequency] - Frequency of the wave in Hz
876
882
  * @param {number} [amplitude] - Amplitude (max height) of the wave
877
883
  * @param {number} [t=time] - Value to use for time of the wave
884
+ * @param {number} [offset] - Value to use for time offset of the wave
878
885
  * @return {number} - Value waving between 0 and amplitude
879
886
  * @memberof Utilities */
880
- function wave(frequency=1, amplitude=1, t=time)
881
- { return amplitude/2 * (1 - Math.cos(t*frequency*2*PI)); }
887
+ function wave(frequency=1, amplitude=1, t=time, offset=0)
888
+ { return amplitude/2 * (1 - Math.cos(offset + t*frequency*2*PI)); }
882
889
 
883
890
  /** Formats seconds to mm:ss style for display purposes
884
891
  * @param {number} t - time in seconds
@@ -1535,9 +1542,7 @@ class Color
1535
1542
  /** Checks if this is a valid color
1536
1543
  * @return {boolean} */
1537
1544
  isValid()
1538
- {
1539
- return isNumber(this.r) && isNumber(this.g) && isNumber(this.b) && isNumber(this.a);
1540
- }
1545
+ { return isNumber(this.r) && isNumber(this.g) && isNumber(this.b) && isNumber(this.a); }
1541
1546
  }
1542
1547
 
1543
1548
  ///////////////////////////////////////////////////////////////////////////////
@@ -1700,6 +1705,13 @@ let cameraScale = 32;
1700
1705
  ///////////////////////////////////////////////////////////////////////////////
1701
1706
  // Display settings
1702
1707
 
1708
+ /** Enable applying color to tiles when using canvas2d
1709
+ * - This is slower but should be the same as webgl rendering
1710
+ * @type {boolean}
1711
+ * @default
1712
+ * @memberof Settings */
1713
+ let canvasColorTiles = true;
1714
+
1703
1715
  /** The max size of the canvas, centered if window is larger
1704
1716
  * @type {Vector2}
1705
1717
  * @default Vector2(1920,1080)
@@ -1713,14 +1725,21 @@ let canvasMaxSize = vec2(1920, 1080);
1713
1725
  * @memberof Settings */
1714
1726
  let canvasFixedSize = vec2();
1715
1727
 
1716
- /** Use nearest neighbor canvas scaling for more pixelated look
1717
- * - Must be set before startup to take effect
1728
+ /** Use nearest canvas scaling for more pixelated look
1718
1729
  * - If enabled sets css image-rendering:pixelated
1719
1730
  * @type {boolean}
1720
1731
  * @default
1721
1732
  * @memberof Settings */
1722
1733
  let canvasPixelated = true;
1723
1734
 
1735
+ /** Use nearest canvas scaling for more pixelated look
1736
+ * - If enabled sets css image-rendering:pixelated
1737
+ * - This defaults to false because text looks better with smoothing
1738
+ * @type {boolean}
1739
+ * @default
1740
+ * @memberof Settings */
1741
+ let overlayCanvasPixelated = false;
1742
+
1724
1743
  /** Disables texture filtering for crisper pixel art
1725
1744
  * @type {boolean}
1726
1745
  * @default
@@ -1961,6 +1980,14 @@ function setCameraAngle(angle) { cameraAngle = angle; }
1961
1980
  * @memberof Settings */
1962
1981
  function setCameraScale(scale) { cameraScale = scale; }
1963
1982
 
1983
+ /** Set if tiles should be colorized when using canvas2d
1984
+ * This can be slower but results should look nearly identical to webgl rendering
1985
+ * It can be enabled/disabled at any time
1986
+ * Optimized for performance, and will use faster method if color is white or untextured
1987
+ * @param {boolean} colorTiles
1988
+ * @memberof Settings */
1989
+ function setCanvasColorTiles(colorTiles) { canvasColorTiles = colorTiles; }
1990
+
1964
1991
  /** Set max size of the canvas
1965
1992
  * @param {Vector2} size
1966
1993
  * @memberof Settings */
@@ -1971,10 +1998,30 @@ function setCanvasMaxSize(size) { canvasMaxSize = size; }
1971
1998
  * @memberof Settings */
1972
1999
  function setCanvasFixedSize(size) { canvasFixedSize = size; }
1973
2000
 
1974
- /** Use nearest neighbor scaling algorithm for canvas for more pixelated look
2001
+ /** Use nearest scaling algorithm for canvas for more pixelated look
2002
+ * - If enabled sets css image-rendering:pixelated
2003
+ * @param {boolean} pixelated
2004
+ * @memberof Settings */
2005
+ function setCanvasPixelated(pixelated)
2006
+ {
2007
+ canvasPixelated = pixelated;
2008
+ if (mainCanvas)
2009
+ mainCanvas.style.imageRendering = pixelated ? 'pixelated' : '';
2010
+ if (glCanvas)
2011
+ glCanvas.style.imageRendering = pixelated ? 'pixelated' : '';
2012
+ }
2013
+
2014
+ /** Use nearest scaling algorithm for canvas for more pixelated look
2015
+ * - If enabled sets css image-rendering:pixelated
2016
+ * - This defaults to false because text looks better with smoothing
1975
2017
  * @param {boolean} pixelated
1976
2018
  * @memberof Settings */
1977
- function setCanvasPixelated(pixelated) { canvasPixelated = pixelated; }
2019
+ function setOverlayCanvasPixelated(pixelated)
2020
+ {
2021
+ overlayCanvasPixelated = pixelated;
2022
+ if (overlayCanvas)
2023
+ overlayCanvas.style.imageRendering = pixelated ? 'pixelated' : '';
2024
+ }
1978
2025
 
1979
2026
  /** Disables texture filtering for crisper pixel art
1980
2027
  * @param {boolean} pixelated
@@ -1999,7 +2046,12 @@ function setHeadlessMode(headless) { headlessMode = headless; }
1999
2046
  /** Set if webgl rendering is enabled
2000
2047
  * @param {boolean} enable
2001
2048
  * @memberof Settings */
2002
- function setGLEnable(enable) { glEnable = enable; }
2049
+ function setGLEnable(enable)
2050
+ {
2051
+ glEnable = enable;
2052
+ if (glCanvas) // hide glCanvas if webgl is disabled
2053
+ glCanvas.style.visibility = enable ? 'visible' : 'hidden';
2054
+ }
2003
2055
 
2004
2056
  /** Set default size of tiles in pixels
2005
2057
  * @param {Vector2} size
@@ -2363,7 +2415,7 @@ class EngineObject
2363
2415
  if (o.mass) // push away if not fixed
2364
2416
  o.velocity = o.velocity.subtract(velocity);
2365
2417
 
2366
- debugOverlay && debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f00');
2418
+ debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f00');
2367
2419
  continue;
2368
2420
  }
2369
2421
 
@@ -2425,7 +2477,7 @@ class EngineObject
2425
2477
  else // bounce if other object is fixed
2426
2478
  this.velocity.x *= -restitution;
2427
2479
  }
2428
- debugOverlay && debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f0f');
2480
+ debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f0f');
2429
2481
  }
2430
2482
  }
2431
2483
  if (this.collideTiles)
@@ -2470,7 +2522,7 @@ class EngineObject
2470
2522
  this.pos.x = oldPos.x;
2471
2523
  this.velocity.x *= -this.restitution;
2472
2524
  }
2473
- debugOverlay && debugPhysics && debugRect(this.pos, this.size, '#f00');
2525
+ debugPhysics && debugRect(this.pos, this.size, '#f00');
2474
2526
  }
2475
2527
  }
2476
2528
  }
@@ -2672,6 +2724,16 @@ let drawCanvas;
2672
2724
  * @memberof Draw */
2673
2725
  let drawContext;
2674
2726
 
2727
+ /** Offscreen canvas that can be used for image processing
2728
+ * @type {OffscreenCanvas}
2729
+ * @memberof Draw */
2730
+ let workCanvas;
2731
+
2732
+ /** Offscreen canvas that can be used for image processing
2733
+ * @type {OffscreenCanvasRenderingContext2D}
2734
+ * @memberof Draw */
2735
+ let workContext;
2736
+
2675
2737
  /** The size of the main canvas (and other secondary canvases)
2676
2738
  * @type {Vector2}
2677
2739
  * @memberof Draw */
@@ -2691,10 +2753,10 @@ let drawCount;
2691
2753
  * Create a tile info object using a grid based system
2692
2754
  * - This can take vecs or floats for easier use and conversion
2693
2755
  * - If an index is passed in, the tile size and index will determine the position
2694
- * @param {Vector2|number} [pos=0] - Index of tile in sheet
2756
+ * @param {Vector2|number} [pos=0] - Index of tile in sheet
2695
2757
  * @param {Vector2|number} [size=tileSizeDefault] - Size of tile in pixels
2696
- * @param {number} [textureIndex] - Texture index to use
2697
- * @param {number} [padding] - How many pixels padding around tiles
2758
+ * @param {number} [textureIndex] - Texture index to use
2759
+ * @param {number} [padding] - How many pixels padding around tiles
2698
2760
  * @return {TileInfo}
2699
2761
  * @example
2700
2762
  * tile(2) // a tile at index 2 using the default tile size of 16
@@ -2821,55 +2883,7 @@ class TextureInfo
2821
2883
  }
2822
2884
 
2823
2885
  ///////////////////////////////////////////////////////////////////////////////
2824
-
2825
- /** Convert from screen to world space coordinates
2826
- * @param {Vector2} screenPos
2827
- * @return {Vector2}
2828
- * @memberof Draw */
2829
- function screenToWorld(screenPos)
2830
- {
2831
- let cameraPosRelativeX = (screenPos.x - mainCanvasSize.x/2 + .5) / cameraScale;
2832
- let cameraPosRelativeY = (screenPos.y - mainCanvasSize.y/2 + .5) / -cameraScale;
2833
- if (cameraAngle)
2834
- {
2835
- // apply camera rotation
2836
- const cos = Math.cos(-cameraAngle), sin = Math.sin(-cameraAngle);
2837
- const rotatedX = cameraPosRelativeX * cos - cameraPosRelativeY * sin;
2838
- const rotatedY = cameraPosRelativeX * sin + cameraPosRelativeY * cos;
2839
- cameraPosRelativeX = rotatedX;
2840
- cameraPosRelativeY = rotatedY;
2841
- }
2842
- return new Vector2(cameraPosRelativeX + cameraPos.x, cameraPosRelativeY + cameraPos.y);
2843
- }
2844
-
2845
- /** Convert from world to screen space coordinates
2846
- * @param {Vector2} worldPos
2847
- * @return {Vector2}
2848
- * @memberof Draw */
2849
- function worldToScreen(worldPos)
2850
- {
2851
- let cameraPosRelativeX = worldPos.x - cameraPos.x;
2852
- let cameraPosRelativeY = worldPos.y - cameraPos.y;
2853
- if (cameraAngle)
2854
- {
2855
- // apply inverse camera rotation
2856
- const cos = Math.cos(cameraAngle), sin = Math.sin(cameraAngle);
2857
- const rotatedX = cameraPosRelativeX * cos - cameraPosRelativeY * sin;
2858
- const rotatedY = cameraPosRelativeX * sin + cameraPosRelativeY * cos;
2859
- cameraPosRelativeX = rotatedX;
2860
- cameraPosRelativeY = rotatedY;
2861
- }
2862
- return new Vector2
2863
- (
2864
- cameraPosRelativeX * cameraScale + mainCanvasSize.x/2 - .5,
2865
- cameraPosRelativeY * -cameraScale + mainCanvasSize.y/2 - .5
2866
- );
2867
- }
2868
-
2869
- /** Get the camera's visible area in world space
2870
- * @return {Vector2}
2871
- * @memberof Draw */
2872
- function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
2886
+ // Drawing functions
2873
2887
 
2874
2888
  /** Draw textured tile centered in world space, with color applied if using WebGL
2875
2889
  * @param {Vector2} pos - Center of the tile in world space
@@ -2892,6 +2906,9 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=new Color,
2892
2906
  ASSERT(isColor(color) && (!additiveColor || isColor(additiveColor)), 'drawTile color is invalid');
2893
2907
  ASSERT(isNumber(angle), 'drawTile angle should be a number');
2894
2908
 
2909
+ if (color.a <= 0 && (!additiveColor || additiveColor.a <= 0) || !size.x || !size.y)
2910
+ return; // completely invisible, skip render
2911
+
2895
2912
  const textureInfo = tileInfo && tileInfo.textureInfo;
2896
2913
  if (useWebGL)
2897
2914
  {
@@ -2942,18 +2959,15 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=new Color,
2942
2959
  if (textureInfo)
2943
2960
  {
2944
2961
  // calculate uvs and render
2945
- const x = tileInfo.pos.x + tileFixBleedScale;
2946
- const y = tileInfo.pos.y + tileFixBleedScale;
2947
- const w = tileInfo.size.x - 2*tileFixBleedScale;
2948
- const h = tileInfo.size.y - 2*tileFixBleedScale;
2949
- context.globalAlpha = color.a; // only alpha is supported
2950
- context.drawImage(textureInfo.image, x, y, w, h, -.5, -.5, 1, 1);
2951
- context.globalAlpha = 1; // set back to full alpha
2962
+ const x = tileInfo.pos.x, y = tileInfo.pos.y;
2963
+ const w = tileInfo.size.x, h = tileInfo.size.y;
2964
+ drawImageColor(context, textureInfo.image, x, y, w, h, -.5, -.5, 1, 1, color, additiveColor);
2952
2965
  }
2953
2966
  else
2954
2967
  {
2955
- // if no tile info, force untextured
2956
- context.fillStyle = color.toString();
2968
+ // if no tile info, use untextured rect
2969
+ const c = additiveColor ? color.add(additiveColor) : color;
2970
+ context.fillStyle = c.toString();
2957
2971
  context.fillRect(-.5, -.5, 1, 1);
2958
2972
  }
2959
2973
  }, screenSpace, context);
@@ -2966,7 +2980,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=new Color,
2966
2980
  * @param {Color} [color=(1,1,1,1)]
2967
2981
  * @param {number} [angle]
2968
2982
  * @param {boolean} [useWebGL=glEnable]
2969
- * @param {boolean} [screenSpace=false]
2983
+ * @param {boolean} [screenSpace]
2970
2984
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
2971
2985
  * @memberof Draw */
2972
2986
  function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
@@ -2979,73 +2993,90 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
2979
2993
  * @param {Vector2} posB
2980
2994
  * @param {number} [thickness]
2981
2995
  * @param {Color} [color=(1,1,1,1)]
2996
+ * @param {Vector2} [pos=(0,0)] - Offset to apply
2997
+ * @param {number} [angle] - Angle to rotate by
2982
2998
  * @param {boolean} [useWebGL=glEnable]
2983
- * @param {boolean} [screenSpace=false]
2999
+ * @param {boolean} [screenSpace]
2984
3000
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
2985
3001
  * @memberof Draw */
2986
- function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, context)
3002
+ function drawLine(posA, posB, thickness=.1, color, pos=vec2(), angle=0, useWebGL, screenSpace, context)
2987
3003
  {
2988
3004
  const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
2989
3005
  const size = vec2(thickness, halfDelta.length()*2);
2990
- drawRect(posA.add(halfDelta), size, color, halfDelta.angle(), useWebGL, screenSpace, context);
3006
+ pos = pos.add(posA.add(halfDelta));
3007
+ angle += halfDelta.angle();
3008
+ drawRect(pos, size, color, angle, useWebGL, screenSpace, context);
2991
3009
  }
2992
3010
 
2993
3011
  /** Draw colored polygon using passed in points
2994
- * @param {Array<Vector2>} points - Array of Vector2 points
3012
+ * @param {Array<Vector2>} points - Array of Vector2 points
2995
3013
  * @param {Color} [color=(1,1,1,1)]
2996
- * @param {number} [lineWidth=0]
3014
+ * @param {number} [lineWidth]
2997
3015
  * @param {Color} [lineColor=(0,0,0,1)]
2998
- * @param {boolean} [screenSpace=false]
3016
+ * @param {Vector2} [pos=(0,0)] - Offset to apply
3017
+ * @param {number} [angle] - Angle to rotate by
3018
+ * @param {boolean} [useWebGL] - Webgl not supported
3019
+ * @param {boolean} [screenSpace]
2999
3020
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
3000
3021
  * @memberof Draw */
3001
- function drawPoly(points, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=drawContext)
3022
+ function drawPoly(points, color=new Color, lineWidth=0, lineColor=BLACK, pos=vec2(), angle=0, useWebGL=false, screenSpace=false, context=drawContext)
3002
3023
  {
3003
- ASSERT(isColor(color) && isColor(lineColor));
3004
- context.fillStyle = color.toString();
3005
- context.beginPath();
3006
- for (const point of screenSpace ? points : points.map(worldToScreen))
3007
- context.lineTo(point.x, point.y);
3008
- context.closePath();
3009
- context.fill();
3010
- if (lineWidth)
3024
+ ASSERT(isVector2(pos) && pos.isValid(), 'drawPoly pos should be a vec2');
3025
+ ASSERT(Array.isArray(points), 'drawPoly points should be an array');
3026
+ ASSERT(isColor(color) && isColor(lineColor), 'drawPoly color is invalid');
3027
+ ASSERT(isNumber(lineWidth), 'drawPoly lineWidth should be a number');
3028
+ ASSERT(isNumber(angle), 'drawPoly angle should be a number');
3029
+ ASSERT(!useWebGL, 'drawPoly webgl not supported');
3030
+ drawCanvas2D(pos, vec2(1), angle, false, context=>
3011
3031
  {
3012
- context.strokeStyle = lineColor.toString();
3013
- context.lineWidth = screenSpace ? lineWidth : lineWidth*cameraScale;
3014
- context.stroke();
3015
- }
3032
+ context.beginPath();
3033
+ for (const point of points)
3034
+ context.lineTo(point.x, point.y);
3035
+ context.closePath();
3036
+ context.fillStyle = color.toString();
3037
+ context.fill();
3038
+ if (lineWidth)
3039
+ {
3040
+ context.strokeStyle = lineColor.toString();
3041
+ context.lineWidth = lineWidth;
3042
+ context.stroke();
3043
+ }
3044
+ }, screenSpace, context);
3016
3045
  }
3017
3046
 
3018
3047
  /** Draw colored ellipse using passed in point
3019
3048
  * @param {Vector2} pos
3020
- * @param {number} [width=1]
3021
- * @param {number} [height=1]
3022
- * @param {number} [angle=0]
3049
+ * @param {Vector2} [size=(1,1)]
3023
3050
  * @param {Color} [color=(1,1,1,1)]
3024
- * @param {number} [lineWidth=0]
3051
+ * @param {number} [angle]
3052
+ * @param {number} [lineWidth]
3025
3053
  * @param {Color} [lineColor=(0,0,0,1)]
3026
- * @param {boolean} [screenSpace=false]
3054
+ * @param {boolean} [useWebGL] - Webgl not supported
3055
+ * @param {boolean} [screenSpace]
3027
3056
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
3028
3057
  * @memberof Draw */
3029
- function drawEllipse(pos, width=1, height=1, angle=0, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=drawContext)
3058
+ function drawEllipse(pos, size=vec2(1), color=new Color, angle=0, lineWidth=0, lineColor=BLACK, useWebGL=false, screenSpace=false, context=drawContext)
3030
3059
  {
3031
- ASSERT(isColor(color) && isColor(lineColor));
3032
- if (!screenSpace)
3033
- {
3034
- pos = worldToScreen(pos);
3035
- width *= cameraScale;
3036
- height *= cameraScale;
3037
- lineWidth *= cameraScale;
3038
- }
3039
- context.fillStyle = color.toString();
3040
- context.beginPath();
3041
- context.ellipse(pos.x, pos.y, width, height, angle, 0, 9);
3042
- context.fill();
3043
- if (lineWidth)
3060
+ ASSERT(isVector2(pos) && pos.isValid(), 'drawEllipse pos should be a vec2');
3061
+ ASSERT(isVector2(size) && size.isValid(), 'drawEllipse size should be a vec2');
3062
+ ASSERT(isColor(color) && isColor(lineColor), 'drawEllipse color is invalid');
3063
+ ASSERT(isNumber(angle), 'drawEllipse angle should be a number');
3064
+ ASSERT(isNumber(lineWidth), 'drawEllipse lineWidth should be a number');
3065
+ ASSERT(lineWidth>=0 && lineWidth < size.x && lineWidth < size.y, 'drawEllipse invalid lineWidth');
3066
+ ASSERT(!useWebGL, 'drawEllipse webgl not supported');
3067
+ drawCanvas2D(pos, vec2(1), angle, false, context=>
3044
3068
  {
3045
- context.strokeStyle = lineColor.toString();
3046
- context.lineWidth = lineWidth;
3047
- context.stroke();
3048
- }
3069
+ context.beginPath();
3070
+ context.ellipse(0, 0, size.y, size.x, 0, 0, 9);
3071
+ context.fillStyle = color.toString();
3072
+ context.fill();
3073
+ if (lineWidth)
3074
+ {
3075
+ context.strokeStyle = lineColor.toString();
3076
+ context.lineWidth = lineWidth;
3077
+ context.stroke();
3078
+ }
3079
+ }, screenSpace, context);
3049
3080
  }
3050
3081
 
3051
3082
  /** Draw colored circle using passed in point
@@ -3054,11 +3085,12 @@ function drawEllipse(pos, width=1, height=1, angle=0, color=new Color, lineWidth
3054
3085
  * @param {Color} [color=(1,1,1,1)]
3055
3086
  * @param {number} [lineWidth=0]
3056
3087
  * @param {Color} [lineColor=(0,0,0,1)]
3088
+ * @param {boolean} [useWebGL] - Webgl not supported
3057
3089
  * @param {boolean} [screenSpace=false]
3058
3090
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
3059
3091
  * @memberof Draw */
3060
- function drawCircle(pos, radius=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=drawContext)
3061
- { drawEllipse(pos, radius, radius, 0, color, lineWidth, lineColor, screenSpace, context); }
3092
+ function drawCircle(pos, radius=1, color=new Color, lineWidth=0, lineColor=BLACK, useWebGL=false, screenSpace, context=drawContext)
3093
+ { drawEllipse(pos, vec2(radius), color, 0, lineWidth, lineColor, useWebGL, screenSpace, context); }
3062
3094
 
3063
3095
  /** Draw directly to a 2d canvas context in world space
3064
3096
  * @param {Vector2} pos
@@ -3085,6 +3117,9 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
3085
3117
  context.restore();
3086
3118
  }
3087
3119
 
3120
+ ///////////////////////////////////////////////////////////////////////////////
3121
+ // Text Drawing Functions
3122
+
3088
3123
  /** Draw text on main canvas in world space
3089
3124
  * Automatically splits new lines into rows
3090
3125
  * @param {string} text
@@ -3133,7 +3168,7 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
3133
3168
  * @param {number} [maxWidth]
3134
3169
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
3135
3170
  * @memberof Draw */
3136
- function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, maxWidth=undefined, context=overlayContext)
3171
+ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, maxWidth=undefined, context=overlayContext)
3137
3172
  {
3138
3173
  context.fillStyle = color.toString();
3139
3174
  context.strokeStyle = lineColor.toString();
@@ -3154,22 +3189,67 @@ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineCol
3154
3189
  });
3155
3190
  }
3156
3191
 
3192
+ ///////////////////////////////////////////////////////////////////////////////
3193
+ // Drawing utilities
3194
+
3195
+ /** Convert from screen to world space coordinates
3196
+ * @param {Vector2} screenPos
3197
+ * @return {Vector2}
3198
+ * @memberof Draw */
3199
+ function screenToWorld(screenPos)
3200
+ {
3201
+ let cameraPosRelativeX = (screenPos.x - mainCanvasSize.x/2 + .5) / cameraScale;
3202
+ let cameraPosRelativeY = (screenPos.y - mainCanvasSize.y/2 + .5) / -cameraScale;
3203
+ if (cameraAngle)
3204
+ {
3205
+ // apply camera rotation
3206
+ const cos = Math.cos(-cameraAngle), sin = Math.sin(-cameraAngle);
3207
+ const rotatedX = cameraPosRelativeX * cos - cameraPosRelativeY * sin;
3208
+ const rotatedY = cameraPosRelativeX * sin + cameraPosRelativeY * cos;
3209
+ cameraPosRelativeX = rotatedX;
3210
+ cameraPosRelativeY = rotatedY;
3211
+ }
3212
+ return new Vector2(cameraPosRelativeX + cameraPos.x, cameraPosRelativeY + cameraPos.y);
3213
+ }
3214
+
3215
+ /** Convert from world to screen space coordinates
3216
+ * @param {Vector2} worldPos
3217
+ * @return {Vector2}
3218
+ * @memberof Draw */
3219
+ function worldToScreen(worldPos)
3220
+ {
3221
+ let cameraPosRelativeX = worldPos.x - cameraPos.x;
3222
+ let cameraPosRelativeY = worldPos.y - cameraPos.y;
3223
+ if (cameraAngle)
3224
+ {
3225
+ // apply inverse camera rotation
3226
+ const cos = Math.cos(cameraAngle), sin = Math.sin(cameraAngle);
3227
+ const rotatedX = cameraPosRelativeX * cos - cameraPosRelativeY * sin;
3228
+ const rotatedY = cameraPosRelativeX * sin + cameraPosRelativeY * cos;
3229
+ cameraPosRelativeX = rotatedX;
3230
+ cameraPosRelativeY = rotatedY;
3231
+ }
3232
+ return new Vector2
3233
+ (
3234
+ cameraPosRelativeX * cameraScale + mainCanvasSize.x/2 - .5,
3235
+ cameraPosRelativeY * -cameraScale + mainCanvasSize.y/2 - .5
3236
+ );
3237
+ }
3238
+
3239
+ /** Get the camera's visible area in world space
3240
+ * @return {Vector2}
3241
+ * @memberof Draw */
3242
+ function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
3243
+
3157
3244
  /** Enable normal or additive blend mode
3158
3245
  * @param {boolean} [additive]
3159
- * @param {boolean} [useWebGL=glEnable]
3160
3246
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
3161
3247
  * @memberof Draw */
3162
- function setBlendMode(additive=false, useWebGL=glEnable, context)
3248
+ function setBlendMode(additive=false, context)
3163
3249
  {
3164
- ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
3165
- if (useWebGL)
3166
- glAdditive = additive;
3167
- else
3168
- {
3169
- if (!context)
3170
- context = drawContext;
3171
- context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
3172
- }
3250
+ glAdditive = additive;
3251
+ context ||= drawContext;
3252
+ context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
3173
3253
  }
3174
3254
 
3175
3255
  /** Combines all LittleJS canvases onto the main canvas and clears them
@@ -3186,6 +3266,102 @@ function combineCanvases()
3186
3266
  overlayCanvas.width |= 0;
3187
3267
  }
3188
3268
 
3269
+ /** Helper function to draw an image with color and additive color applied
3270
+ * This is slower then normal drawImage when color is applied
3271
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
3272
+ * @param {HTMLImageElement|OffscreenCanvas} image
3273
+ * @param {number} sx
3274
+ * @param {number} sy
3275
+ * @param {number} sWidth
3276
+ * @param {number} sHeight
3277
+ * @param {number} dx
3278
+ * @param {number} dy
3279
+ * @param {number} dWidth
3280
+ * @param {number} dHeight
3281
+ * @param {Color} color
3282
+ * @param {Color} [additiveColor]
3283
+ * @memberof Draw */
3284
+ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight, color, additiveColor)
3285
+ {
3286
+ function isWhite(c) { return c.r >= 1 && c.g >= 1 && c.b >= 1; }
3287
+ function isBlack(c) { return c.r <= 0 && c.g <= 0 && c.b <= 0 && c.a <= 0; }
3288
+ const sx2 = tileFixBleedScale;
3289
+ const sy2 = tileFixBleedScale;
3290
+ const sWidth2 = sWidth - 2*tileFixBleedScale;
3291
+ const sHeight2 = sHeight - 2*tileFixBleedScale;
3292
+ if (!canvasColorTiles || (additiveColor ? isWhite(color.add(additiveColor)) && additiveColor.a <= 0 : isWhite(color)))
3293
+ {
3294
+ // white texture with no additive alpha, no need to tint
3295
+ context.globalAlpha = color.a;
3296
+ context.drawImage(image, sx+sx2, sy+sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
3297
+ context.globalAlpha = 1;
3298
+ }
3299
+ else
3300
+ {
3301
+ // copy to offscreen canvas
3302
+ workCanvas.width = sWidth;
3303
+ workCanvas.height = sHeight;
3304
+ workContext.drawImage(image, sx, sy, sWidth, sHeight, 0, 0, sWidth, sHeight);
3305
+
3306
+ // tint image using offscreen work context
3307
+ const imageData = workContext.getImageData(0, 0, sWidth, sHeight);
3308
+ const data = imageData.data;
3309
+ if (additiveColor && !isBlack(additiveColor))
3310
+ {
3311
+ // slower path with additive color
3312
+ const colorMultiply = [color.r, color.g, color.b, color.a];
3313
+ const colorAdd = [additiveColor.r * 255, additiveColor.g * 255, additiveColor.b * 255, additiveColor.a * 255];
3314
+ for (let i = 0; i < data.length; ++i)
3315
+ data[i] = data[i] * colorMultiply[i&3] + colorAdd[i&3] |0;
3316
+ workContext.putImageData(imageData, 0, 0);
3317
+ context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
3318
+ }
3319
+ else
3320
+ {
3321
+ // faster path with no additive color
3322
+ for (let i = 0; i < data.length; i+=4)
3323
+ {
3324
+ data[i ] *= color.r;
3325
+ data[i+1] *= color.g;
3326
+ data[i+2] *= color.b;
3327
+ }
3328
+ workContext.putImageData(imageData, 0, 0);
3329
+ context.globalAlpha = color.a;
3330
+ context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
3331
+ context.globalAlpha = 1;
3332
+ }
3333
+ }
3334
+ }
3335
+
3336
+
3337
+ /** Returns true if fullscreen mode is active
3338
+ * @return {boolean}
3339
+ * @memberof Draw */
3340
+ function isFullscreen() { return !!document.fullscreenElement; }
3341
+
3342
+ /** Toggle fullscreen mode
3343
+ * @memberof Draw */
3344
+ function toggleFullscreen()
3345
+ {
3346
+ const rootElement = mainCanvas.parentElement;
3347
+ if (isFullscreen())
3348
+ {
3349
+ if (document.exitFullscreen)
3350
+ document.exitFullscreen();
3351
+ }
3352
+ else if (rootElement.requestFullscreen)
3353
+ rootElement.requestFullscreen();
3354
+ }
3355
+
3356
+ /** Set the cursor style
3357
+ * @param {string} cursorStyle - CSS cursor style (auto, none, crosshair, etc)
3358
+ * @memberof Draw */
3359
+ function setCursor(cursorStyle = 'auto')
3360
+ {
3361
+ const rootElement = mainCanvas.parentElement;
3362
+ rootElement.style.cursor = cursorStyle;
3363
+ }
3364
+
3189
3365
  ///////////////////////////////////////////////////////////////////////////////
3190
3366
 
3191
3367
  let engineFontImage;
@@ -3253,7 +3429,7 @@ class FontImage
3253
3429
  (text+'').split('\n').forEach((line, i)=>
3254
3430
  {
3255
3431
  const centerOffset = center ? line.length * size.x * scale / 2 |0 : 0;
3256
- for(let j=line.length; j--;)
3432
+ for (let j=line.length; j--;)
3257
3433
  {
3258
3434
  // draw each character
3259
3435
  let charCode = line[j].charCodeAt(0);
@@ -3272,37 +3448,6 @@ class FontImage
3272
3448
 
3273
3449
  context.restore();
3274
3450
  }
3275
- }
3276
-
3277
- ///////////////////////////////////////////////////////////////////////////////
3278
- // Display functions
3279
-
3280
- /** Returns true if fullscreen mode is active
3281
- * @return {boolean}
3282
- * @memberof Draw */
3283
- function isFullscreen() { return !!document.fullscreenElement; }
3284
-
3285
- /** Toggle fullscreen mode
3286
- * @memberof Draw */
3287
- function toggleFullscreen()
3288
- {
3289
- const rootElement = mainCanvas.parentElement;
3290
- if (isFullscreen())
3291
- {
3292
- if (document.exitFullscreen)
3293
- document.exitFullscreen();
3294
- }
3295
- else if (rootElement.requestFullscreen)
3296
- rootElement.requestFullscreen();
3297
- }
3298
-
3299
- /** Set the cursor style
3300
- * @param {string} cursorStyle - CSS cursor style (auto, none, crosshair, etc)
3301
- * @memberof Draw */
3302
- function setCursor(cursorStyle = 'auto')
3303
- {
3304
- const rootElement = mainCanvas.parentElement;
3305
- rootElement.style.cursor = cursorStyle;
3306
3451
  }
3307
3452
  /**
3308
3453
  * LittleJS Input System
@@ -3854,7 +3999,7 @@ function touchGamepadRender()
3854
3999
  }
3855
4000
  else // draw cross shaped gamepad
3856
4001
  {
3857
- for(let i=10; i--;)
4002
+ for (let i=10; i--;)
3858
4003
  {
3859
4004
  const angle = i*PI/4;
3860
4005
  context.arc(leftCenter.x, leftCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
@@ -3898,7 +4043,7 @@ function pointerLockExit() { document.exitPointerLock && document.exitPointerLoc
3898
4043
  /** Check if pointer is locked (true if locked)
3899
4044
  * @return {boolean}
3900
4045
  * @memberof Input */
3901
- function pointerLockIsActive() { return document.pointerLockElement == mainCanvas }
4046
+ function pointerLockIsActive() { return document.pointerLockElement == mainCanvas; }
3902
4047
  /**
3903
4048
  * LittleJS Audio System
3904
4049
  * - <a href=https://killedbyapixel.github.io/ZzFX/>ZzFX Sound Effects</a> - ZzFX Sound Effect Generator
@@ -4322,7 +4467,7 @@ function zzfxG
4322
4467
  repeatTime = repeatTime * sampleRate | 0;
4323
4468
 
4324
4469
  // generate waveform
4325
- for(length = attack + decay + sustain + release + delay | 0;
4470
+ for (length = attack + decay + sustain + release + delay | 0;
4326
4471
  i < length; b[i++] = s * volume) // sample
4327
4472
  {
4328
4473
  if (!(++crush%(bitCrush*100|0))) // bit crush
@@ -5215,10 +5360,17 @@ class ParticleEmitter extends EngineObject
5215
5360
  const particle = new Particle(pos, this.tileInfo, angle, colorStart, colorEnd, particleTime, sizeStart, sizeEnd, this.fadeRate, this.additive, this.trailScale, this.localSpace && this, this.particleDestroyCallback);
5216
5361
  particle.velocity = vec2().setAngle(velocityAngle, speed);
5217
5362
  particle.angleVelocity = angleSpeed;
5363
+ if (!this.localSpace)
5364
+ {
5365
+ // apply emitter velocity to particle
5366
+ particle.velocity.x += this.velocity.x;
5367
+ particle.velocity.y += this.velocity.y;
5368
+ particle.angleVelocity += this.angleVelocity;
5369
+ }
5218
5370
  particle.fadeRate = this.fadeRate;
5219
5371
  particle.damping = this.damping;
5220
5372
  particle.angleDamping = this.angleDamping;
5221
- particle.restitution = this.restitution;
5373
+ particle.restitution = this.restitution;
5222
5374
  particle.friction = this.friction;
5223
5375
  particle.gravityScale = this.gravityScale;
5224
5376
  particle.collideTiles = this.collideTiles;
@@ -5505,7 +5657,7 @@ class Medal
5505
5657
  context.save();
5506
5658
  context.beginPath();
5507
5659
  context.fillStyle = new Color(.9,.9,.9).toString();
5508
- context.strokeStyle = new Color(0,0,0).toString();
5660
+ context.strokeStyle = BLACK.toString();
5509
5661
  context.lineWidth = 3;
5510
5662
  context.rect(x, y, width, height);
5511
5663
  context.fill();
@@ -5522,11 +5674,11 @@ class Medal
5522
5674
  const descriptionSize = height*.3;
5523
5675
  const pos = vec2(x + medalDisplayIconSize + 2*gap.x, y + gap.y*2 + nameSize/2);
5524
5676
  const textWidth = width - medalDisplayIconSize - 3*gap.x;
5525
- drawTextScreen(this.name, pos, nameSize, new Color(0,0,0), 0, undefined, 'left', undefined, textWidth);
5677
+ drawTextScreen(this.name, pos, nameSize, BLACK, 0, undefined, 'left', undefined, textWidth);
5526
5678
 
5527
5679
  // draw the description
5528
5680
  pos.y = y + height - gap.y*2 - descriptionSize/2;
5529
- drawTextScreen(this.description, pos, descriptionSize, new Color(0,0,0), 0, undefined, 'left', undefined, textWidth);
5681
+ drawTextScreen(this.description, pos, descriptionSize, BLACK, 0, undefined, 'left', undefined, textWidth);
5530
5682
  context.restore();
5531
5683
  }
5532
5684
 
@@ -5540,7 +5692,7 @@ class Medal
5540
5692
  if (this.image)
5541
5693
  overlayContext.drawImage(this.image, pos.x-size/2, pos.y-size/2, size, size);
5542
5694
  else
5543
- drawTextScreen(this.icon, pos, size*.7, new Color(0,0,0));
5695
+ drawTextScreen(this.icon, pos, size*.7, BLACK);
5544
5696
  }
5545
5697
 
5546
5698
  // Get local storage key used by the medal
@@ -5549,12 +5701,13 @@ class Medal
5549
5701
  /**
5550
5702
  * LittleJS WebGL Interface
5551
5703
  * - All webgl used by the engine is wrapped up here
5704
+ * - Will fall back to 2D canvas rendering if webgl is not supported
5552
5705
  * - For normal stuff you won't need to see or call anything in this file
5553
5706
  * - For advanced stuff there are helper functions to create shaders, textures, etc
5554
5707
  * - Can be disabled with glEnable to revert to 2D canvas rendering
5555
5708
  * - Batches sprite rendering on GPU for incredibly fast performance
5556
5709
  * - Sprite transform math is done in the shader where possible
5557
- * - Supports shadertoy style post processing shaders
5710
+ * - Supports shadertoy style post processing shaders via plugin
5558
5711
  * @namespace WebGL
5559
5712
  */
5560
5713
 
@@ -5593,6 +5746,14 @@ function glInit()
5593
5746
  glCanvas = document.createElement('canvas');
5594
5747
  glContext = glCanvas.getContext('webgl2', {antialias:glAntialias});
5595
5748
 
5749
+ if (!glContext)
5750
+ {
5751
+ console.warn('WebGL2 not supported, falling back to 2D canvas rendering!');
5752
+ glCanvas = glContext = undefined;
5753
+ glEnable = false;
5754
+ return;
5755
+ }
5756
+
5596
5757
  // create the webgl canvas
5597
5758
  const rootElement = mainCanvas.parentElement;
5598
5759
  rootElement.appendChild(glCanvas);
@@ -5642,7 +5803,7 @@ function glInit()
5642
5803
  // Also used by tile layer rendering when redrawing tiles
5643
5804
  function glPreRender()
5644
5805
  {
5645
- if (!glEnable || headlessMode) return;
5806
+ if (!glEnable || !glContext) return;
5646
5807
 
5647
5808
  // set up the shader and canvas
5648
5809
  glClearCanvas();
@@ -5693,6 +5854,8 @@ function glPreRender()
5693
5854
  * @memberof WebGL */
5694
5855
  function glClearCanvas()
5695
5856
  {
5857
+ if (!glContext) return;
5858
+
5696
5859
  // clear and set to same size as main canvas
5697
5860
  glContext.viewport(0, 0, glCanvas.width=drawCanvas.width, glCanvas.height=drawCanvas.height);
5698
5861
  glContext.clear(glContext.COLOR_BUFFER_BIT);
@@ -5706,7 +5869,7 @@ function glClearCanvas()
5706
5869
  function glSetTexture(texture, wrap=false)
5707
5870
  {
5708
5871
  // must flush cache with the old texture to set a new one
5709
- if (headlessMode || texture == glActiveTexture)
5872
+ if (!glContext || texture == glActiveTexture)
5710
5873
  return;
5711
5874
 
5712
5875
  glFlush();
@@ -5725,6 +5888,8 @@ function glSetTexture(texture, wrap=false)
5725
5888
  * @memberof WebGL */
5726
5889
  function glCompileShader(source, type)
5727
5890
  {
5891
+ if (!glContext) return;
5892
+
5728
5893
  // build the shader
5729
5894
  const shader = glContext.createShader(type);
5730
5895
  glContext.shaderSource(shader, source);
@@ -5743,6 +5908,8 @@ function glCompileShader(source, type)
5743
5908
  * @memberof WebGL */
5744
5909
  function glCreateProgram(vsSource, fsSource)
5745
5910
  {
5911
+ if (!glContext) return;
5912
+
5746
5913
  // build the program
5747
5914
  const program = glContext.createProgram();
5748
5915
  glContext.attachShader(program, glCompileShader(vsSource, glContext.VERTEX_SHADER));
@@ -5761,6 +5928,8 @@ function glCreateProgram(vsSource, fsSource)
5761
5928
  * @memberof WebGL */
5762
5929
  function glCreateTexture(image)
5763
5930
  {
5931
+ if (!glContext) return;
5932
+
5764
5933
  // build the texture
5765
5934
  const texture = glContext.createTexture();
5766
5935
  glContext.bindTexture(glContext.TEXTURE_2D, texture);
@@ -5796,6 +5965,7 @@ function glCreateTexture(image)
5796
5965
  * @memberof WebGL */
5797
5966
  function glDeleteTexture(texture)
5798
5967
  {
5968
+ if (!glContext) return;
5799
5969
  glContext.deleteTexture(texture);
5800
5970
  }
5801
5971
 
@@ -5805,6 +5975,8 @@ function glDeleteTexture(texture)
5805
5975
  * @memberof WebGL */
5806
5976
  function glSetTextureData(texture, image)
5807
5977
  {
5978
+ if (!glContext) return;
5979
+
5808
5980
  // build the texture
5809
5981
  ASSERT(!!image && image.width > 0, 'Invalid image data.');
5810
5982
  glContext.bindTexture(glContext.TEXTURE_2D, texture);
@@ -5815,7 +5987,7 @@ function glSetTextureData(texture, image)
5815
5987
  * @memberof WebGL */
5816
5988
  function glFlush()
5817
5989
  {
5818
- if (!glEnable || !glInstanceCount) return;
5990
+ if (!glEnable || !glContext || !glInstanceCount) return;
5819
5991
 
5820
5992
  const destBlend = glBatchAdditive ? glContext.ONE : glContext.ONE_MINUS_SRC_ALPHA;
5821
5993
  glContext.blendFuncSeparate(glContext.SRC_ALPHA, destBlend, glContext.ONE, destBlend);
@@ -5835,7 +6007,7 @@ function glFlush()
5835
6007
  * @memberof WebGL */
5836
6008
  function glCopyToContext(context)
5837
6009
  {
5838
- if (!glEnable)
6010
+ if (!glEnable || !glContext)
5839
6011
  return;
5840
6012
 
5841
6013
  glFlush();
@@ -6090,6 +6262,13 @@ class PostProcessPlugin
6090
6262
  postProcess = this;
6091
6263
 
6092
6264
  if (headlessMode) return;
6265
+
6266
+ if (!glEnable)
6267
+ {
6268
+ console.warn('PostProcessPlugin: WebGL not enabled!');
6269
+ return;
6270
+ }
6271
+
6093
6272
  if (!shaderCode) // default shader pass through
6094
6273
  shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
6095
6274
 
@@ -6399,7 +6578,7 @@ class UISystemPlugin
6399
6578
 
6400
6579
  function updateInvisible(o)
6401
6580
  {
6402
- for(const c of o.children)
6581
+ for (const c of o.children)
6403
6582
  updateInvisible(c);
6404
6583
  o.updateInvisible();
6405
6584
  }
@@ -6415,7 +6594,7 @@ class UISystemPlugin
6415
6594
  if (o.parent)
6416
6595
  o.pos = o.localPos.add(o.parent.pos);
6417
6596
  // update in reverse order to detect mouse enter/leave
6418
- for(let i=o.children.length; i--;)
6597
+ for (let i=o.children.length; i--;)
6419
6598
  updateObject(o.children[i]);
6420
6599
  o.update();
6421
6600
  }
@@ -6433,7 +6612,7 @@ class UISystemPlugin
6433
6612
  if (o.parent)
6434
6613
  o.pos = o.localPos.add(o.parent.pos);
6435
6614
  o.render();
6436
- for(const c of o.children)
6615
+ for (const c of o.children)
6437
6616
  renderObject(c);
6438
6617
  }
6439
6618
  uiSystem.uiObjects.forEach(o=> o.parent || renderObject(o));
@@ -6490,7 +6669,7 @@ class UISystemPlugin
6490
6669
  * @param {boolean} [mirror] */
6491
6670
  drawTile(pos, size, tileInfo, color=uiSystem.defaultColor, angle=0, mirror=false)
6492
6671
  {
6493
- drawTile(pos, size, tileInfo, color, angle, mirror, BLACK, false, true, uiSystem.uiContext);
6672
+ drawTile(pos, size, tileInfo, color, angle, mirror, CLEAR_BLACK, false, true, uiSystem.uiContext);
6494
6673
  }
6495
6674
 
6496
6675
  /** Draw text to the UI context
@@ -6940,7 +7119,7 @@ class Box2dObject extends EngineObject
6940
7119
  bodyDef.set_angle(-angle);
6941
7120
  this.body = box2d.world.CreateBody(bodyDef);
6942
7121
  this.body.object = this;
6943
- this.outlineColor = BLACK;
7122
+ this.lineColor = BLACK;
6944
7123
  }
6945
7124
 
6946
7125
  /** Destroy this object and it's physics body */
@@ -6967,7 +7146,7 @@ class Box2dObject extends EngineObject
6967
7146
  if (this.tileInfo)
6968
7147
  super.render();
6969
7148
  else
6970
- this.drawFixtures(this.color, this.outlineColor, this.lineWidth, mainContext);
7149
+ this.drawFixtures(this.color, this.lineColor, this.lineWidth, mainContext);
6971
7150
  }
6972
7151
 
6973
7152
  /** Render debug info */
@@ -6981,13 +7160,13 @@ class Box2dObject extends EngineObject
6981
7160
 
6982
7161
  /** Draws all this object's fixtures
6983
7162
  * @param {Color} [color]
6984
- * @param {Color} [outlineColor]
7163
+ * @param {Color} [lineColor]
6985
7164
  * @param {number} [lineWidth]
6986
7165
  * @param {CanvasRenderingContext2D} [context] */
6987
- drawFixtures(color=WHITE, outlineColor, lineWidth=.1, context)
7166
+ drawFixtures(color=WHITE, lineColor, lineWidth=.1, context)
6988
7167
  {
6989
7168
  this.getFixtureList().forEach(fixture=>
6990
- box2d.drawFixture(fixture, this.pos, this.angle, color, outlineColor, lineWidth, context));
7169
+ box2d.drawFixture(fixture, this.pos, this.angle, color, lineColor, lineWidth, context));
6991
7170
  }
6992
7171
 
6993
7172
  ///////////////////////////////////////////////////////////////////////////////
@@ -8468,10 +8647,10 @@ class Box2dPlugin
8468
8647
  * @param {Vector2} pos
8469
8648
  * @param {number} angle
8470
8649
  * @param {Color} [color]
8471
- * @param {Color} [outlineColor]
8650
+ * @param {Color} [lineColor]
8472
8651
  * @param {number} [lineWidth]
8473
8652
  * @param {CanvasRenderingContext2D} [context] */
8474
- drawFixture(fixture, pos, angle, color=WHITE, outlineColor=BLACK, lineWidth=.1, context=drawContext)
8653
+ drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1, context=drawContext)
8475
8654
  {
8476
8655
  const shape = box2d.castObjectType(fixture.GetShape());
8477
8656
  switch (shape.GetType())
@@ -8481,101 +8660,25 @@ class Box2dPlugin
8481
8660
  let points = [];
8482
8661
  for (let i=shape.GetVertexCount(); i--;)
8483
8662
  points.push(box2d.vec2From(shape.GetVertex(i)));
8484
- box2d.drawPoly(pos, angle, points, color, outlineColor, lineWidth, context);
8663
+ drawPoly(points, color, lineWidth, lineColor, pos, angle, false, false, context);
8485
8664
  break;
8486
8665
  }
8487
8666
  case box2d.instance.b2Shape.e_circle:
8488
8667
  {
8489
8668
  const radius = shape.get_m_radius();
8490
- box2d.drawCircle(pos, radius, color, outlineColor, lineWidth, context);
8669
+ drawCircle(pos, radius, color, lineWidth, lineColor, false, false, context);
8491
8670
  break;
8492
8671
  }
8493
8672
  case box2d.instance.b2Shape.e_edge:
8494
8673
  {
8495
8674
  const v1 = box2d.vec2From(shape.get_m_vertex1());
8496
8675
  const v2 = box2d.vec2From(shape.get_m_vertex2());
8497
- box2d.drawLine(pos, angle, v1, v2, color, lineWidth, context);
8676
+ drawLine(v1, v2, lineWidth, lineColor, pos, angle, false, false, context);
8498
8677
  break;
8499
8678
  }
8500
8679
  }
8501
8680
  }
8502
8681
 
8503
- /** draws a circle
8504
- * @param {Vector2} pos
8505
- * @param {number} radius
8506
- * @param {Color} [color]
8507
- * @param {Color} [outlineColor]
8508
- * @param {number} [lineWidth]
8509
- * @param {CanvasRenderingContext2D} [context] */
8510
- drawCircle(pos, radius, color=WHITE, outlineColor=BLACK, lineWidth=.1, context=drawContext)
8511
- {
8512
- drawCanvas2D(pos, vec2(1), 0, 0, context=>
8513
- {
8514
- context.beginPath();
8515
- context.arc(0, 0, radius, 0, 9);
8516
- box2d.drawFillStroke(color, outlineColor, lineWidth, context);
8517
- }, 0, context);
8518
- }
8519
-
8520
- /** draws a polygon
8521
- * @param {Vector2} pos
8522
- * @param {number} angle
8523
- * @param {Array<Vector2>} points
8524
- * @param {Color} [color]
8525
- * @param {Color} [outlineColor]
8526
- * @param {number} [lineWidth]
8527
- * @param {CanvasRenderingContext2D} [context] */
8528
- drawPoly(pos, angle, points, color=WHITE, outlineColor=BLACK, lineWidth=.1, context=drawContext)
8529
- {
8530
- drawCanvas2D(pos, vec2(1), angle, 0, context=>
8531
- {
8532
- context.beginPath();
8533
- points.forEach(p=>context.lineTo(p.x, p.y));
8534
- context.closePath();
8535
- box2d.drawFillStroke(color, outlineColor, lineWidth, context);
8536
- }, 0, context);
8537
- }
8538
-
8539
- /** draws a line
8540
- * @param {Vector2} pos
8541
- * @param {number} angle
8542
- * @param {Vector2} posA
8543
- * @param {Vector2} posB
8544
- * @param {Color} [color]
8545
- * @param {number} [lineWidth]
8546
- * @param {CanvasRenderingContext2D} [context] */
8547
- drawLine(pos, angle, posA, posB, color=WHITE, lineWidth=.1, context=drawContext)
8548
- {
8549
- drawCanvas2D(pos, vec2(1), angle, 0, context=>
8550
- {
8551
- context.beginPath();
8552
- context.lineTo(posA.x, posA.y);
8553
- context.lineTo(posB.x, posB.y);
8554
- box2d.drawFillStroke(0, color, lineWidth, context);
8555
- }, 0, context);
8556
- }
8557
-
8558
- /** performs a fill or stroke as a helper to the other draw functions
8559
- * @param {Color} [color]
8560
- * @param {Color} [outlineColor]
8561
- * @param {number} [lineWidth]
8562
- * @param {CanvasRenderingContext2D} [context] */
8563
- drawFillStroke(color=WHITE, outlineColor=BLACK, lineWidth=.1, context=drawContext)
8564
- {
8565
- if (color)
8566
- {
8567
- context.fillStyle = color.toString();
8568
- context.fill();
8569
- }
8570
- if (outlineColor && lineWidth)
8571
- {
8572
- context.lineWidth = lineWidth;
8573
- context.lineJoin = context.lineCap = 'round';
8574
- context.strokeStyle = outlineColor.toString();
8575
- context.stroke();
8576
- }
8577
- }
8578
-
8579
8682
  ///////////////////////////////////////////////////////////////////////////////
8580
8683
  // helper functions
8581
8684
 
@@ -8676,6 +8779,7 @@ async function box2dInit()
8676
8779
  function setupDebugDraw()
8677
8780
  {
8678
8781
  // setup debug draw
8782
+ const debugLineWidth = .1;
8679
8783
  const debugDraw = new box2d.instance.JSDraw();
8680
8784
  const box2dColor = (c)=> new Color(c.get_r(), c.get_g(), c.get_b());
8681
8785
  const box2dColorPointer = (c)=>
@@ -8693,33 +8797,33 @@ async function box2dInit()
8693
8797
  color = getDebugColor(color);
8694
8798
  point1 = box2d.vec2FromPointer(point1);
8695
8799
  point2 = box2d.vec2FromPointer(point2);
8696
- box2d.drawLine(vec2(), 0, point1, point2, color, undefined, overlayContext);
8800
+ drawLine(point1, point2, debugLineWidth, color, vec2(), 0, false, false, overlayContext);
8697
8801
  };
8698
8802
  debugDraw.DrawPolygon = function(vertices, vertexCount, color)
8699
8803
  {
8700
8804
  color = getDebugColor(color);
8701
8805
  const points = getPointsList(vertices, vertexCount);
8702
- box2d.drawPoly(vec2(), 0, points, undefined, color, undefined, overlayContext);
8806
+ drawPoly(points, CLEAR_WHITE, debugLineWidth, color, vec2(), 0, false, false, overlayContext);
8703
8807
  };
8704
8808
  debugDraw.DrawSolidPolygon = function(vertices, vertexCount, color)
8705
8809
  {
8706
8810
  color = getDebugColor(color);
8707
8811
  const points = getPointsList(vertices, vertexCount);
8708
- box2d.drawPoly(vec2(), 0, points, color, color, undefined, overlayContext);
8812
+ drawPoly(points, color, 0, color, vec2(), 0, false, false, overlayContext);
8709
8813
  };
8710
8814
  debugDraw.DrawCircle = function(center, radius, color)
8711
8815
  {
8712
8816
  color = getDebugColor(color);
8713
8817
  center = box2d.vec2FromPointer(center);
8714
- box2d.drawCircle(center, radius, undefined, color, undefined, overlayContext);
8818
+ drawCircle(center, radius, CLEAR_WHITE, debugLineWidth, color, false, false, overlayContext);
8715
8819
  };
8716
8820
  debugDraw.DrawSolidCircle = function(center, radius, axis, color)
8717
8821
  {
8718
8822
  color = getDebugColor(color);
8719
8823
  center = box2d.vec2FromPointer(center);
8720
8824
  axis = box2d.vec2FromPointer(axis).scale(radius);
8721
- box2d.drawCircle(center, radius, color, color, undefined, overlayContext);
8722
- box2d.drawLine(center, 0, vec2(), axis, color, undefined, overlayContext);
8825
+ drawCircle(center, radius, color, debugLineWidth, color, false, false, overlayContext);
8826
+ drawLine(vec2(), axis, debugLineWidth, color, center, 0, false, false, overlayContext);
8723
8827
  };
8724
8828
  debugDraw.DrawTransform = function(transform)
8725
8829
  {
@@ -8728,8 +8832,8 @@ async function box2dInit()
8728
8832
  const angle = -transform.get_q().GetAngle();
8729
8833
  const p1 = vec2(1,0), c1 = rgb(.75,0,0,.8);
8730
8834
  const p2 = vec2(0,1), c2 = rgb(0,.75,0,.8);
8731
- box2d.drawLine(pos, angle, vec2(), p1, c1, undefined, overlayContext);
8732
- box2d.drawLine(pos, angle, vec2(), p2, c2, undefined, overlayContext);
8835
+ drawLine(vec2(), p1, debugLineWidth, c1, pos, angle, false, false, overlayContext);
8836
+ drawLine(vec2(), p2, debugLineWidth, c2, pos, angle, false, false, overlayContext);
8733
8837
  }
8734
8838
 
8735
8839
  debugDraw.AppendFlags(box2d.instance.b2Draw.e_shapeBit);
@@ -8754,12 +8858,13 @@ async function box2dInit()
8754
8858
  * @param {Vector2} pos - Screen space position
8755
8859
  * @param {Vector2} size - Screen space size
8756
8860
  * @param {TileInfo} startTile - Starting tile for the nine-slice pattern
8757
- * @param {number} [borderSize=1] - Width of the border sections
8758
- * @param {number} [extraSpace=.01] - Extra spacing adjustment
8861
+ * @param {number} [borderSize] - Width of the border sections
8862
+ * @param {number} [extraSpace] - Extra spacing adjustment
8863
+ * @param {number} [angle] - Angle to rotate by
8759
8864
  * @memberof DrawUtilities */
8760
- function drawNineSliceScreen(pos, size, startTile, borderSize=32, extraSpace=1)
8865
+ function drawNineSliceScreen(pos, size, startTile, borderSize=32, extraSpace=2, angle=0)
8761
8866
  {
8762
- drawNineSlice(pos, size, startTile, WHITE, borderSize, BLACK, extraSpace, false, true, overlayContext);
8867
+ drawNineSlice(pos, size, startTile, WHITE, borderSize, BLACK, extraSpace, angle, false, true, overlayContext);
8763
8868
  }
8764
8869
 
8765
8870
  /** Draw a scalable nine-slice UI element in world space
@@ -8768,14 +8873,15 @@ function drawNineSliceScreen(pos, size, startTile, borderSize=32, extraSpace=1)
8768
8873
  * @param {Vector2} size - World space size
8769
8874
  * @param {TileInfo} startTile - Starting tile for the nine-slice pattern
8770
8875
  * @param {Color} [color] - Color to modulate with
8771
- * @param {number} [borderSize=1] - Width of the border sections
8876
+ * @param {number} [borderSize] - Width of the border sections
8772
8877
  * @param {Color} [additiveColor] - Additive color
8773
- * @param {number} [extraSpace=.01] - Extra spacing adjustment
8878
+ * @param {number} [extraSpace] - Extra spacing adjustment
8879
+ * @param {number} [angle] - Angle to rotate by
8774
8880
  * @param {boolean} [useWebGL=glEnable] - Use WebGL for rendering
8775
8881
  * @param {boolean} [screenSpace] - Use screen space coordinates
8776
8882
  * @param {CanvasRenderingContext2D} [context] - Canvas context to use
8777
8883
  * @memberof DrawUtilities */
8778
- function drawNineSlice(pos, size, startTile, color, borderSize=1, additiveColor, extraSpace=.01, useWebGL=glEnable, screenSpace, context)
8884
+ function drawNineSlice(pos, size, startTile, color, borderSize=1, additiveColor, extraSpace=.05, angle=0, useWebGL=glEnable, screenSpace, context)
8779
8885
  {
8780
8886
  // setup nine slice tiles
8781
8887
  const centerTile = startTile.offset(startTile.size);
@@ -8783,26 +8889,27 @@ function drawNineSlice(pos, size, startTile, color, borderSize=1, additiveColor,
8783
8889
  const cornerSize = vec2(borderSize);
8784
8890
  const cornerOffset = size.scale(.5).subtract(cornerSize.scale(.5));
8785
8891
  const flip = screenSpace ? -1 : 1;
8892
+ const rotateAngle = screenSpace ? -angle : angle;
8786
8893
 
8787
8894
  // center
8788
- drawTile(pos, centerSize, centerTile, color, 0, false, additiveColor, useWebGL, screenSpace, context);
8789
- for(let i=4; i--;)
8895
+ drawTile(pos, centerSize, centerTile, color, angle, false, additiveColor, useWebGL, screenSpace, context);
8896
+ for (let i=4; i--;)
8790
8897
  {
8791
8898
  // sides
8792
8899
  const horizontal = i%2;
8793
8900
  const sidePos = cornerOffset.multiply(vec2(horizontal?i==1?1:-1:0, horizontal?0:i?-1:1));
8794
8901
  const sideSize = vec2(horizontal ? borderSize : centerSize.x, horizontal ? centerSize.y : borderSize);
8795
8902
  const sideTile = centerTile.offset(startTile.size.multiply(vec2(i==1?1:i==3?-1:0,i==0?-flip:i==2?flip:0)))
8796
- drawTile(pos.add(sidePos), sideSize, sideTile, color, 0, false, additiveColor, useWebGL, screenSpace, context);
8903
+ drawTile(pos.add(sidePos.rotate(rotateAngle)), sideSize, sideTile, color, angle, false, additiveColor, useWebGL, screenSpace, context);
8797
8904
  }
8798
- for(let i=4; i--;)
8905
+ for (let i=4; i--;)
8799
8906
  {
8800
8907
  // corners
8801
8908
  const flipX = i>1;
8802
8909
  const flipY = i && i<3;
8803
8910
  const cornerPos = cornerOffset.multiply(vec2(flipX?-1:1, flipY?-1:1));
8804
8911
  const cornerTile = centerTile.offset(startTile.size.multiply(vec2(flipX?-1:1,flipY?flip:-flip)));
8805
- drawTile(pos.add(cornerPos), cornerSize, cornerTile, color, 0, false, additiveColor, useWebGL, screenSpace, context);
8912
+ drawTile(pos.add(cornerPos.rotate(rotateAngle)), cornerSize, cornerTile, color, angle, false, additiveColor, useWebGL, screenSpace, context);
8806
8913
  }
8807
8914
  }
8808
8915
 
@@ -8811,12 +8918,13 @@ function drawNineSlice(pos, size, startTile, color, borderSize=1, additiveColor,
8811
8918
  * @param {Vector2} pos - Screen space position
8812
8919
  * @param {Vector2} size - Screen space size
8813
8920
  * @param {TileInfo} startTile - Starting tile for the three-slice pattern
8814
- * @param {number} [borderSize=1] - Width of the border sections
8815
- * @param {number} [extraSpace=.01] - Extra spacing adjustment
8921
+ * @param {number} [borderSize] - Width of the border sections
8922
+ * @param {number} [extraSpace] - Extra spacing adjustment
8923
+ * @param {number} [angle] - Angle to rotate by
8816
8924
  * @memberof DrawUtilities */
8817
- function drawThreeSliceScreen(pos, size, startTile, borderSize=32, extraSpace=1)
8925
+ function drawThreeSliceScreen(pos, size, startTile, borderSize=32, extraSpace=2, angle=0)
8818
8926
  {
8819
- drawThreeSlice(pos, size, startTile, WHITE, borderSize, BLACK, extraSpace, false, true, overlayContext);
8927
+ drawThreeSlice(pos, size, startTile, WHITE, borderSize, BLACK, extraSpace, angle, false, true, overlayContext);
8820
8928
  }
8821
8929
 
8822
8930
  /** Draw a scalable three-slice UI element in world space
@@ -8825,14 +8933,15 @@ function drawThreeSliceScreen(pos, size, startTile, borderSize=32, extraSpace=1)
8825
8933
  * @param {Vector2} size - World space size
8826
8934
  * @param {TileInfo} startTile - Starting tile for the three-slice pattern
8827
8935
  * @param {Color} [color] - Color to modulate with
8828
- * @param {number} [borderSize=1] - Width of the border sections
8936
+ * @param {number} [borderSize] - Width of the border sections
8829
8937
  * @param {Color} [additiveColor] - Additive color
8830
- * @param {number} [extraSpace=.01] - Extra spacing adjustment
8938
+ * @param {number} [extraSpace] - Extra spacing adjustment
8939
+ * @param {number} [angle] - Angle to rotate by
8831
8940
  * @param {boolean} [useWebGL=glEnable] - Use WebGL for rendering
8832
8941
  * @param {boolean} [screenSpace] - Use screen space coordinates
8833
8942
  * @param {CanvasRenderingContext2D} [context] - Canvas context to use
8834
8943
  * @memberof DrawUtilities */
8835
- function drawThreeSlice(pos, size, startTile, color, borderSize=1, additiveColor, extraSpace=.01, useWebGL=glEnable, screenSpace, context)
8944
+ function drawThreeSlice(pos, size, startTile, color, borderSize=1, additiveColor, extraSpace=.05, angle=0, useWebGL=glEnable, screenSpace, context)
8836
8945
  {
8837
8946
  // setup three slice tiles
8838
8947
  const cornerTile = startTile.frame(0);
@@ -8842,25 +8951,26 @@ function drawThreeSlice(pos, size, startTile, color, borderSize=1, additiveColor
8842
8951
  const cornerSize = vec2(borderSize);
8843
8952
  const cornerOffset = size.scale(.5).subtract(cornerSize.scale(.5));
8844
8953
  const flip = screenSpace ? -1 : 1;
8954
+ const rotateAngle = screenSpace ? -angle : angle;
8845
8955
 
8846
8956
  // center
8847
- drawTile(pos, centerSize, centerTile, color, 0, false, additiveColor, useWebGL, screenSpace, context);
8848
- for(let i=4; i--;)
8957
+ drawTile(pos, centerSize, centerTile, color, angle, false, additiveColor, useWebGL, screenSpace, context);
8958
+ for (let i=4; i--;)
8849
8959
  {
8850
8960
  // sides
8851
- const angle = i*PI/2;
8961
+ const a = angle + i*PI/2;
8852
8962
  const horizontal = i%2;
8853
8963
  const sidePos = cornerOffset.multiply(vec2(horizontal?i==1?1:-1:0, horizontal?0:i?-flip:flip));
8854
8964
  const sideSize = vec2(horizontal ? centerSize.y : centerSize.x, borderSize);
8855
- drawTile(pos.add(sidePos), sideSize, sideTile, color, angle, false, additiveColor, useWebGL, screenSpace, context);
8965
+ drawTile(pos.add(sidePos.rotate(rotateAngle)), sideSize, sideTile, color, a, false, additiveColor, useWebGL, screenSpace, context);
8856
8966
  }
8857
- for(let i=4; i--;)
8967
+ for (let i=4; i--;)
8858
8968
  {
8859
8969
  // corners
8860
- const angle = i*PI/2;
8970
+ const a = angle + i*PI/2;
8861
8971
  const flipX = !i || i>2;
8862
8972
  const flipY = i>1;
8863
8973
  const cornerPos = cornerOffset.multiply(vec2(flipX?-1:1, flipY?-flip:flip));
8864
- drawTile(pos.add(cornerPos), cornerSize, cornerTile, color, angle, false, additiveColor, useWebGL, screenSpace, context);
8974
+ drawTile(pos.add(cornerPos.rotate(rotateAngle)), cornerSize, cornerTile, color, a, false, additiveColor, useWebGL, screenSpace, context);
8865
8975
  }
8866
8976
  }