littlejsengine 1.1.26 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -82,27 +82,27 @@ const mod = (a, b=1)=> ((a % b) + b) % b;
82
82
 
83
83
  /** Clamps the value beween max and min
84
84
  * @param {Number} value
85
- * @param {Number} [max=1]
86
85
  * @param {Number} [min=0]
86
+ * @param {Number} [max=1]
87
87
  * @return {Number}
88
88
  * @memberof Utilities */
89
- const clamp = (v, max=1, min=0)=> (ASSERT(max > min), v < min ? min : v > max ? max : v);
89
+ const clamp = (v, min=0, max=1)=> v < min ? min : v > max ? max : v;
90
90
 
91
91
  /** Returns what percentage the value is between max and min
92
92
  * @param {Number} value
93
- * @param {Number} [max=1]
94
93
  * @param {Number} [min=0]
94
+ * @param {Number} [max=1]
95
95
  * @return {Number}
96
96
  * @memberof Utilities */
97
- const percent = (v, max=1, min=0)=> max-min ? clamp((v-min) / (max-min)) : 0;
97
+ const percent = (v, min=0, max=1)=> max-min ? clamp((v-min) / (max-min)) : 0;
98
98
 
99
99
  /** Linearly interpolates the percent value between max and min
100
100
  * @param {Number} percent
101
- * @param {Number} [max=1]
102
101
  * @param {Number} [min=0]
102
+ * @param {Number} [max=1]
103
103
  * @return {Number}
104
104
  * @memberof Utilities */
105
- const lerp = (p, max=1, min=0)=> min + clamp(p) * (max-min);
105
+ const lerp = (p, min=0, max=1)=> min + clamp(p) * (max-min);
106
106
 
107
107
  /** Applies smoothstep function to the percentage value
108
108
  * @param {Number} value
@@ -117,11 +117,11 @@ const smoothStep = (p)=> p * p * (3 - 2 * p);
117
117
  const nearestPowerOfTwo = (v)=> 2**Math.ceil(Math.log2(v));
118
118
 
119
119
  /** Returns true if two axis aligned bounding boxes are overlapping
120
- * @param {Vector2} pointA - Center of box A
121
- * @param {Vector2} sizeA - Size of box A
122
- * @param {Vector2} pointB - Center of box B
123
- * @param {Vector2} sizeB - Size of box B
124
- * @return {Boolean} - True if overlapping
120
+ * @param {Vector2} pointA - Center of box A
121
+ * @param {Vector2} sizeA - Size of box A
122
+ * @param {Vector2} pointB - Center of box B
123
+ * @param {Vector2} [sizeB] - Size of box B
124
+ * @return {Boolean} - True if overlapping
125
125
  * @memberof Utilities */
126
126
  const isOverlapping = (pA, sA, pB, sB)=> abs(pA.x - pB.x)*2 < sA.x + sB.x & abs(pA.y - pB.y)*2 < sA.y + sB.y;
127
127
 
@@ -510,7 +510,7 @@ class Timer
510
510
 
511
511
  /** Get percentage elapsed based on time it was set to, returns 0 if not set
512
512
  * @return {Number} */
513
- getPercent() { return this.isSet()? percent(this.time - time, 0, this.setTime) : 0; }
513
+ getPercent() { return this.isSet()? percent(this.time - time, this.setTime, 0) : 0; }
514
514
  }
515
515
  /**
516
516
  * LittleJS Engine Settings
@@ -748,7 +748,7 @@ let medalDisplayIconSize = 50;
748
748
  const engineName = 'LittleJS';
749
749
 
750
750
  /** Version of engine */
751
- const engineVersion = '1.1.26';
751
+ const engineVersion = '1.2.0';
752
752
 
753
753
  /** Frames per second to update objects
754
754
  * @default */
@@ -785,7 +785,7 @@ let averageFPS, drawCount;
785
785
  // css text used for elements created by engine
786
786
  const styleBody = 'margin:0;overflow:hidden;background:#000' +
787
787
  ';touch-action:none;user-select:none;-webkit-user-select:none;-moz-user-select:none';
788
- const canvaStyle = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
788
+ const styleCanvas = 'position:absolute';
789
789
 
790
790
  ///////////////////////////////////////////////////////////////////////////////
791
791
 
@@ -810,7 +810,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
810
810
  document.body.style = styleBody;
811
811
  document.body.appendChild(mainCanvas = document.createElement('canvas'));
812
812
  mainContext = mainCanvas.getContext('2d');
813
- mainCanvas.style = canvaStyle;
813
+ mainCanvas.style = styleCanvas;
814
814
 
815
815
  // init stuff and start engine
816
816
  debugInit();
@@ -819,7 +819,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
819
819
  // create overlay canvas for hud to appear above gl canvas
820
820
  document.body.appendChild(overlayCanvas = document.createElement('canvas'));
821
821
  overlayContext = overlayCanvas.getContext('2d');
822
- overlayCanvas.style = canvaStyle;
822
+ overlayCanvas.style = styleCanvas;
823
823
 
824
824
  gameInit();
825
825
  touchGamepadCreate();
@@ -835,7 +835,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
835
835
  let frameTimeDeltaMS = frameTimeMS - frameTimeLastMS;
836
836
  frameTimeLastMS = frameTimeMS;
837
837
  if (debug || showWatermark)
838
- averageFPS = lerp(.05, 1e3/(frameTimeDeltaMS||1), averageFPS || 0);
838
+ averageFPS = lerp(.05, averageFPS || 0, 1e3/(frameTimeDeltaMS||1));
839
839
  if (debug)
840
840
  frameTimeDeltaMS *= keyIsDown(107) ? 5 : keyIsDown(109) ? .2 : 1; // +/- to speed/slow time
841
841
  timeReal += frameTimeDeltaMS / 1e3;
@@ -886,13 +886,24 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
886
886
 
887
887
  // fit to window by adding space on top or bottom if necessary
888
888
  const aspect = innerWidth / innerHeight;
889
- const fixedAspect = mainCanvas.width / mainCanvas.height;
890
- mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
891
- mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
892
- if (glCanvas)
889
+ const fixedAspect = canvasFixedSize.x / canvasFixedSize.y;
890
+
891
+ // clear css
892
+ mainCanvas.style.width = overlayCanvas.style.width =
893
+ mainCanvas.style.height = overlayCanvas.style.height =
894
+ mainCanvas.style.top = overlayCanvas.style.top =
895
+ mainCanvas.style.left = overlayCanvas.style.left = '';
896
+
897
+ // letterbox center canvas
898
+ if (aspect < fixedAspect)
899
+ {
900
+ mainCanvas.style.width = overlayCanvas.style.width = innerWidth;
901
+ mainCanvas.style.top = overlayCanvas.style.top = (innerHeight - innerWidth/fixedAspect)/2;
902
+ }
903
+ else
893
904
  {
894
- glCanvas.style.width = mainCanvas.style.width;
895
- glCanvas.style.height = mainCanvas.style.height;
905
+ mainCanvas.style.height = overlayCanvas.style.height = innerHeight;
906
+ mainCanvas.style.left = overlayCanvas.style.left = (innerWidth - innerHeight*fixedAspect)/2;
896
907
  }
897
908
  }
898
909
  else
@@ -900,6 +911,18 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
900
911
  // clear and set size to same as window
901
912
  mainCanvas.width = min(innerWidth, canvasMaxSize.x);
902
913
  mainCanvas.height = min(innerHeight, canvasMaxSize.y);
914
+
915
+ // center canvas
916
+ overlayCanvas.style.top = mainCanvas.style.top = (innerHeight - mainCanvas.height)/2;
917
+ overlayCanvas.style.left = mainCanvas.style.left = (innerWidth - mainCanvas.width)/2;
918
+ }
919
+
920
+ if (glEnable)
921
+ {
922
+ glCanvas.style.width = mainCanvas.style.width;
923
+ glCanvas.style.height = mainCanvas.style.height;
924
+ glCanvas.style.top = mainCanvas.style.top;
925
+ glCanvas.style.left = mainCanvas.style.left;
903
926
  }
904
927
 
905
928
  // render sort then render while removing destroyed objects
@@ -1042,7 +1065,7 @@ function engineObjectsCallback(pos, size, callbackFunction, objects=engineObject
1042
1065
  class EngineObject
1043
1066
  {
1044
1067
  /** Create an engine object and adds it to the list of objects
1045
- * @param {Vector2} [position=new Vector2(0,0)] - World space position of the object
1068
+ * @param {Vector2} [position=new Vector2()] - World space position of the object
1046
1069
  * @param {Vector2} [size=objectDefaultSize] - World space size of the object
1047
1070
  * @param {Number} [tileIndex=-1] - Tile to use to render object, untextured if -1
1048
1071
  * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
@@ -1087,12 +1110,15 @@ class EngineObject
1087
1110
  this.gravityScale = 1;
1088
1111
  /** @property {Number} [renderOrder=0] - Objects are sorted by render order */
1089
1112
  this.renderOrder = renderOrder;
1113
+ /** @property {Vector2} [velocity=new Vector2()] - Velocity of the object */
1114
+ this.velocity = new Vector2();
1115
+ /** @property {Number} [angleVelocity=0] - Angular velocity of the object */
1116
+ this.angleVelocity = 0;
1090
1117
 
1091
1118
  // init other internal object stuff
1092
1119
  this.spawnTime = time;
1093
- this.velocity = vec2(this.collideSolidObjects = this.angleVelocity = 0);
1094
- this.collideTiles = 1;
1095
1120
  this.children = [];
1121
+ this.collideTiles = 1;
1096
1122
 
1097
1123
  // add to list of objects
1098
1124
  engineObjects.push(this);
@@ -1111,8 +1137,8 @@ class EngineObject
1111
1137
  }
1112
1138
 
1113
1139
  // limit max speed to prevent missing collisions
1114
- this.velocity.x = clamp(this.velocity.x, objectMaxSpeed, -objectMaxSpeed);
1115
- this.velocity.y = clamp(this.velocity.y, objectMaxSpeed, -objectMaxSpeed);
1140
+ this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
1141
+ this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
1116
1142
 
1117
1143
  // apply physics
1118
1144
  const oldPos = this.pos.copy();
@@ -1202,8 +1228,8 @@ class EngineObject
1202
1228
 
1203
1229
  // lerp betwen elastic or inelastic based on elasticity
1204
1230
  const elasticity = max(this.elasticity, o.elasticity);
1205
- this.velocity.y = lerp(elasticity, elastic0, inelastic);
1206
- o.velocity.y = lerp(elasticity, elastic1, inelastic);
1231
+ this.velocity.y = lerp(elasticity, inelastic, elastic0);
1232
+ o.velocity.y = lerp(elasticity, inelastic, elastic1);
1207
1233
  }
1208
1234
  }
1209
1235
  if (!smallStepUp && (isBlockedX || !isBlockedY)) // resolve x collision
@@ -1223,8 +1249,8 @@ class EngineObject
1223
1249
 
1224
1250
  // lerp betwen elastic or inelastic based on elasticity
1225
1251
  const elasticity = max(this.elasticity, o.elasticity);
1226
- this.velocity.x = lerp(elasticity, elastic0, inelastic);
1227
- o.velocity.x = lerp(elasticity, elastic1, inelastic);
1252
+ this.velocity.x = lerp(elasticity, inelastic, elastic0);
1253
+ o.velocity.x = lerp(elasticity, inelastic, elastic1);
1228
1254
  }
1229
1255
  else // bounce if other object is fixed
1230
1256
  this.velocity.x *= -this.elasticity;
@@ -1254,7 +1280,7 @@ class EngineObject
1254
1280
 
1255
1281
  // adjust next velocity to settle on ground
1256
1282
  const o = (oldPos.y - this.size.y/2|0) - (oldPos.y - this.size.y/2);
1257
- if (o < 0 && o > -1 && o > this.damping * this.velocity.y + gravity * this.gravityScale)
1283
+ if (o < 0 && o > this.damping * this.velocity.y + gravity * this.gravityScale)
1258
1284
  this.velocity.y = this.damping ? (o - gravity * this.gravityScale) / this.damping : 0;
1259
1285
 
1260
1286
  // move to previous position
@@ -1565,6 +1591,18 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainCont
1565
1591
  context.restore();
1566
1592
  }
1567
1593
 
1594
+ /** Enable normal or additive blend mode
1595
+ * @param {Boolean} [additive=0]
1596
+ * @param {Boolean} [useWebGL=glEnable]
1597
+ * @memberof Draw */
1598
+ function setBlendMode(additive, useWebGL=glEnable)
1599
+ {
1600
+ if (glEnable && useWebGL)
1601
+ glSetBlendMode(additive);
1602
+ else
1603
+ mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
1604
+ }
1605
+
1568
1606
  /** Draw text on overlay canvas in world space
1569
1607
  * @param {String} text
1570
1608
  * @param {Vector2} pos
@@ -1590,16 +1628,93 @@ function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new
1590
1628
  overlayContext.fillText(text, pos.x, pos.y);
1591
1629
  }
1592
1630
 
1593
- /** Enable normal or additive blend mode
1594
- * @param {Boolean} [additive=0]
1595
- * @param {Boolean} [useWebGL=glEnable]
1596
- * @memberof Draw */
1597
- function setBlendMode(additive, useWebGL=glEnable)
1631
+ ///////////////////////////////////////////////////////////////////////////////
1632
+
1633
+ /**
1634
+ * Font Image Object - Draw text on a 2D canvas by using characters in an image
1635
+ * <br> - 96 characters (from space to tilde) are stored in an image
1636
+ * <br> - Uses a default 8x8 font if none is supplied
1637
+ * <br> - You can also use fonts from the main tile sheet
1638
+ * @example
1639
+ * // use built in font
1640
+ * const font = new ImageFont;
1641
+ *
1642
+ * // draw text
1643
+ * font.drawTextScreen("LittleJS\nHello World!", vec2(200, 50));
1644
+ */
1645
+ class FontImage
1598
1646
  {
1599
- if (glEnable && useWebGL)
1600
- glSetBlendMode(additive);
1601
- else
1602
- mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
1647
+ /** Create an image font
1648
+ * @param {Image} [image] - The image the font is stored in, if undefined the default font is used
1649
+ * @param {Vector2} [tileSize=vec2(8)] - The size of the font source tiles
1650
+ * @param {Vector2} [paddingSize=vec2(0,1)] - How much extra space to add between characters
1651
+ * @param {Number} [startTileIndex=0] - Tile index in image where font starts
1652
+ * @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to
1653
+ */
1654
+ constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), startTileIndex=0, context=overlayContext)
1655
+ {
1656
+ if (!image)
1657
+ {
1658
+ // use default font image
1659
+ image = new Image();
1660
+ image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6MCIBdFY+Q0PMNgf0yCgsSAaZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWw1hPWNRcEyWh2npdpBmSUBrRaN1WSlQAWXaWLF0kkwR7C/N7XUvIaQr4xpeqeQigjLwBRZ/6KkufAACcv5z7GYhvgWHII8xN/4C2hvLCkmZSVgIJMHNTxpYOsJOiitEjUQnIQAFFP+opoQfCbjWmfzWiNzCjEvxAPFARmw/Y5Te0sl2zEp4M+m7cToJjdJS7y1xcFlFT7EtWBZ8nl7uLSff7UgYewtlyY6yHM4RrjNrnsfn/W0DBK9xNZctZ7gsTeXTe5eqLqXFlKnAn+vkNXzH6TqPW10ksKoSlauKk1TcncZLSTxNnQ8Av/8RGPXOFIuMCZ/fpwIZx5vU4QIRQ44HwUyHx/QrZhm5VtXFex7ja2se0KBpnPT0DSwS4xd86uAy+SqnLBgkPY5JolhJ33ENyK6n0VyRzCnzebIkFdY/NUtM66lTdhix1KUss6q5snH5ftVJXG0QITb1gUDvzD3z/BbyOLMirwCtPzAAAAAElFTkSuQmCC';
1661
+ }
1662
+
1663
+ this.image = image;
1664
+ this.tileSize = tileSize;
1665
+ this.paddingSize = paddingSize;
1666
+ this.startTileIndex = startTileIndex;
1667
+ this.context = context;
1668
+ }
1669
+
1670
+ /** Draw text in screen space using the image font
1671
+ * @param {String} text
1672
+ * @param {Vector2} pos
1673
+ * @param {Number} [scale=4]
1674
+ * @param {Boolean} [center]
1675
+ */
1676
+ drawTextScreen(text, pos, scale=4, center)
1677
+ {
1678
+ const context = this.context;
1679
+ context.save();
1680
+ context.imageSmoothingEnabled = !cavasPixelated;
1681
+
1682
+ const size = this.tileSize;
1683
+ const drawSize = size.add(this.paddingSize).scale(scale);
1684
+ const cols = this.image.width / this.tileSize.x |0;
1685
+ text.split('\n').forEach((line, i)=>
1686
+ {
1687
+ const centerOffset = center ? line.length * size.x * scale / 2 |0 : 0;
1688
+ for(let j=line.length; j--;)
1689
+ {
1690
+ // draw each character
1691
+ let charCode = line[j].charCodeAt();
1692
+ if (charCode < 32 || charCode > 127)
1693
+ charCode = 127; // unknown character
1694
+
1695
+ // get the character source location and draw it
1696
+ const tile = this.startTileIndex + charCode - 32;
1697
+ const x = tile % cols;
1698
+ const y = tile / cols |0;
1699
+ const drawPos = pos.add(vec2(j,i).multiply(drawSize));
1700
+ context.drawImage(this.image, x * size.x, y * size.y, size.x, size.y,
1701
+ drawPos.x - centerOffset, drawPos.y, size.x * scale, size.y * scale);
1702
+ }
1703
+ });
1704
+
1705
+ context.restore();
1706
+ }
1707
+
1708
+ /** Draw text in world space using the image font
1709
+ * @param {String} text
1710
+ * @param {Vector2} pos
1711
+ * @param {Number} [scale=.25]
1712
+ * @param {Boolean} [center]
1713
+ */
1714
+ drawText(text, pos, scale=1, center)
1715
+ {
1716
+ this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center);
1717
+ }
1603
1718
  }
1604
1719
 
1605
1720
  ///////////////////////////////////////////////////////////////////////////////
@@ -1790,7 +1905,7 @@ const mouseToScreen = (mousePos)=>
1790
1905
 
1791
1906
  const rect = mainCanvas.getBoundingClientRect();
1792
1907
  return mainCanvasSize.multiply(
1793
- vec2(percent(mousePos.x, rect.right, rect.left), percent(mousePos.y, rect.bottom, rect.top)));
1908
+ vec2(percent(mousePos.x, rect.left, rect.right), percent(mousePos.y, rect.top, rect.bottom)));
1794
1909
  }
1795
1910
 
1796
1911
  ///////////////////////////////////////////////////////////////////////////////
@@ -1831,8 +1946,8 @@ function gamepadsUpdate()
1831
1946
  // read clamp dead zone of analog sticks
1832
1947
  const deadZone = .3, deadZoneMax = .8;
1833
1948
  const applyDeadZone = (v)=>
1834
- v > deadZone ? percent( v, deadZoneMax, deadZone) :
1835
- v < -deadZone ? -percent(-v, deadZoneMax, deadZone) : 0;
1949
+ v > deadZone ? percent( v, deadZone, deadZoneMax) :
1950
+ v < -deadZone ? -percent(-v, deadZone, deadZoneMax) : 0;
1836
1951
 
1837
1952
  // read analog sticks
1838
1953
  for (let j = 0; j < gamepad.axes.length-1; j+=2)
@@ -1912,7 +2027,7 @@ if (isTouchDevice)
1912
2027
  // touch gamepad, virtual on screen gamepad emulator for touch devices
1913
2028
 
1914
2029
  // touch input internal variables
1915
- let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick = vec2();
2030
+ let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick = vec2(), touchGamepadAnalog = 1;
1916
2031
 
1917
2032
  // create the touch gamepad, called automatically by the engine
1918
2033
  function touchGamepadCreate()
@@ -1958,7 +2073,14 @@ function touchGamepadCreate()
1958
2073
  if (touchPos.distance(stickCenter) < touchGamepadSize)
1959
2074
  {
1960
2075
  // virtual analog stick
1961
- touchGamepadStick = touchPos.subtract(stickCenter).clampLength(touchGamepadSize/2).scale(2/touchGamepadSize);
2076
+ if (touchGamepadAnalog)
2077
+ touchGamepadStick = touchPos.subtract(stickCenter).clampLength(touchGamepadSize/2).scale(2/touchGamepadSize);
2078
+ else
2079
+ {
2080
+ // 8 way dpad
2081
+ const angle = touchPos.subtract(stickCenter).angle();
2082
+ touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
2083
+ }
1962
2084
  }
1963
2085
  else if (touchPos.distance(buttonCenter) < touchGamepadSize)
1964
2086
  {
@@ -1982,7 +2104,7 @@ function touchGamepadRender()
1982
2104
  return;
1983
2105
 
1984
2106
  // fade off when not touching or paused
1985
- const alpha = percent(touchGamepadTimer.get(), 3, 4);
2107
+ const alpha = percent(touchGamepadTimer.get(), 4, 3);
1986
2108
  if (!alpha || paused)
1987
2109
  return;
1988
2110
 
@@ -2085,7 +2207,7 @@ class Sound
2085
2207
  return; // out of range
2086
2208
 
2087
2209
  // attenuate volume by distance
2088
- volume *= percent(lengthSquared**.5, range*this.taper, range);
2210
+ volume *= percent(lengthSquared**.5, range, range*this.taper);
2089
2211
  }
2090
2212
 
2091
2213
  // get pan from screen space coords
@@ -2267,7 +2389,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
2267
2389
  // connect source to gain
2268
2390
  (
2269
2391
  window.StereoPannerNode ? // create pan node if possible
2270
- source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, 1, -1)}))
2392
+ source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)}))
2271
2393
  : source
2272
2394
  )
2273
2395
  .connect(gainNode);
@@ -2640,11 +2762,11 @@ class TileLayerData
2640
2762
  class TileLayer extends EngineObject
2641
2763
  {
2642
2764
  /** Create a tile layer object
2643
- * @param {Vector2} [position=new Vector2(0,0)] - World space position
2644
- * @param {Vector2} [size=objectDefaultSize] - World space size
2645
- * @param {Vector2} [tileSize=tileSizeDefault] - Size of tiles in source pixels
2646
- * @param {Vector2} [scale=new Vector2(1,1)] - How much to scale this layer when rendered
2647
- * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
2765
+ * @param {Vector2} [position=new Vector2()] - World space position
2766
+ * @param {Vector2} [size=objectDefaultSize] - World space size
2767
+ * @param {Vector2} [tileSize=tileSizeDefault] - Size of tiles in source pixels
2768
+ * @param {Vector2} [scale=new Vector2(1,1)] - How much to scale this layer when rendered
2769
+ * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
2648
2770
  */
2649
2771
  constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1), renderOrder=0)
2650
2772
  {
@@ -2704,17 +2826,19 @@ class TileLayer extends EngineObject
2704
2826
  );
2705
2827
  }
2706
2828
 
2707
- /** Draw all the tile data to an offscreen canvas */
2829
+ /** Draw all the tile data to an offscreen canvas
2830
+ * - This may be slow in some browsers
2831
+ */
2708
2832
  redraw()
2709
2833
  {
2710
- this.redrawStart();
2834
+ this.redrawStart(1);
2711
2835
  this.drawAllTileData();
2712
2836
  this.redrawEnd();
2713
2837
  }
2714
2838
 
2715
2839
  /** Call to start the redraw process
2716
- * @param {Boolean} [clear=1] - Should it clear the canvas before drawing */
2717
- redrawStart(clear = 1)
2840
+ * @param {Boolean} [clear=0] - Should it clear the canvas before drawing */
2841
+ redrawStart(clear = 0)
2718
2842
  {
2719
2843
  if (clear)
2720
2844
  {
@@ -3445,8 +3569,8 @@ function glInit()
3445
3569
 
3446
3570
  // create the canvas and tile texture
3447
3571
  glCanvas = document.createElement('canvas');
3448
- glContext = glCanvas.getContext('webgl');
3449
- glCanvas.style = canvaStyle;
3572
+ glContext = glCanvas.getContext('webgl', {antialias: false});
3573
+ glCanvas.style = styleCanvas;
3450
3574
  glTileTexture = glCreateTexture(tileImage);
3451
3575
 
3452
3576
  // some browsers are much faster without copying the gl buffer so we just overlay it instead
@@ -3456,14 +3580,13 @@ function glInit()
3456
3580
  glShader = glCreateProgram(
3457
3581
  'precision highp float;'+ // use highp for better accuracy, lowp for better perf
3458
3582
  'uniform mat4 m;'+ // transform matrix
3459
- 'attribute float a;'+ // angle
3460
- 'attribute vec2 p,s,t;'+ // position, size, uv
3461
- 'attribute vec4 c,b;'+ // color, additiveColor
3583
+ 'attribute vec2 p,t;'+ // position, uv
3584
+ 'attribute vec4 c,a;'+ // color, additiveColor
3462
3585
  'varying vec2 v;'+ // return uv
3463
3586
  'varying vec4 d,e;'+ // return color, additiveColor
3464
3587
  'void main(){'+ // shader entry point
3465
- 'gl_Position=m*vec4((s*cos(a)-vec2(-s.y,s)*sin(a))*.5+p,1,1);'+// transform position
3466
- 'v=t;d=c;e=b;'+ // pass stuff to fragment shader
3588
+ 'gl_Position=m*vec4(p,1,1);'+// transform position
3589
+ 'v=t;d=c;e=a;'+ // pass stuff to fragment shader
3467
3590
  '}' // end of shader
3468
3591
  ,
3469
3592
  'precision highp float;'+ // use highp for better accuracy, lowp for better perf
@@ -3490,12 +3613,10 @@ function glInit()
3490
3613
  glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
3491
3614
  offset += size*typeSize;
3492
3615
  }
3493
- initVertexAttribArray('a', gl_FLOAT, 4, 1); // angle
3494
3616
  initVertexAttribArray('p', gl_FLOAT, 4, 2); // position
3495
- initVertexAttribArray('s', gl_FLOAT, 4, 2); // size
3496
3617
  initVertexAttribArray('t', gl_FLOAT, 4, 2); // texture coords
3497
3618
  initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
3498
- initVertexAttribArray('b', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
3619
+ initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
3499
3620
  }
3500
3621
 
3501
3622
  /** Set the WebGl blend mode, normally you should call setBlendMode instead
@@ -3610,6 +3731,7 @@ function glPreRender(width, height, cameraX, cameraY, cameraScale)
3610
3731
 
3611
3732
  // clear and set to same size as main canvas
3612
3733
  glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height);
3734
+ glContext.clear(gl_COLOR_BUFFER_BIT);
3613
3735
 
3614
3736
  // set up the shader
3615
3737
  glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
@@ -3656,12 +3778,10 @@ function glCopyToContext(context, forceDraw)
3656
3778
  if (!glEnable || !glBatchCount) return;
3657
3779
 
3658
3780
  glFlush();
3781
+
3782
+ // do not draw in overlay mode because the canvas is visible
3659
3783
  if (!glOverlay || forceDraw)
3660
- {
3661
- // do not draw/clear in overlay mode because the canvas is visible
3662
3784
  context.drawImage(glCanvas, 0, 0);
3663
- glContext.clear(gl_COLOR_BUFFER_BIT);
3664
- }
3665
3785
  }
3666
3786
 
3667
3787
  /** Add a sprite to the gl draw list, used by all gl draw functions
@@ -3684,51 +3804,49 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=0xffffff
3684
3804
  // flush if there is no room for more verts or if different blend mode
3685
3805
  if (glBatchCount == gl_MAX_BATCH || glBatchAdditive != glAdditive)
3686
3806
  glFlush();
3807
+
3808
+ // prepare to create the verts from size and angle
3809
+ const c = Math.cos(angle)/2, s = Math.sin(angle)/2;
3810
+ const cX = c*sizeX, cY = c*sizeY, sX = s*sizeX, sY = s*sizeY;
3687
3811
 
3688
3812
  // setup 2 triangles to form a quad
3689
3813
  let offset = glBatchCount++ * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT;
3690
3814
 
3691
3815
  // vertex 0
3692
- glPositionData[offset++] = angle;
3693
- glPositionData[offset++] = x; glPositionData[offset++] = y;
3694
- glPositionData[offset++] = -sizeX; glPositionData[offset++] = -sizeY;
3695
- glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
3696
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3816
+ glPositionData[offset++] = x - cX - sY;
3817
+ glPositionData[offset++] = y - cY + sX;
3818
+ glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
3819
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3697
3820
 
3698
3821
  // vertex 1
3699
- glPositionData[offset++] = angle;
3700
- glPositionData[offset++] = x; glPositionData[offset++] = y;
3701
- glPositionData[offset++] = sizeX; glPositionData[offset++] = sizeY;
3702
- glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
3703
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3822
+ glPositionData[offset++] = x + cX + sY;
3823
+ glPositionData[offset++] = y + cY - sX;
3824
+ glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
3825
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3704
3826
 
3705
3827
  // vertex 2
3706
- glPositionData[offset++] = angle;
3707
- glPositionData[offset++] = x; glPositionData[offset++] = y;
3708
- glPositionData[offset++] = -sizeX; glPositionData[offset++] = sizeY;
3709
- glPositionData[offset++] = uv0X; glPositionData[offset++] = uv0Y;
3710
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3828
+ glPositionData[offset++] = x - cX + sY;
3829
+ glPositionData[offset++] = y + cY + sX;
3830
+ glPositionData[offset++] = uv0X; glPositionData[offset++] = uv0Y;
3831
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3711
3832
 
3712
3833
  // vertex 0
3713
- glPositionData[offset++] = angle;
3714
- glPositionData[offset++] = x; glPositionData[offset++] = y;
3715
- glPositionData[offset++] = -sizeX; glPositionData[offset++] = -sizeY;
3716
- glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
3717
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3834
+ glPositionData[offset++] = x - cX - sY;
3835
+ glPositionData[offset++] = y - cY + sX;
3836
+ glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
3837
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3718
3838
 
3719
3839
  // vertex 3
3720
- glPositionData[offset++] = angle;
3721
- glPositionData[offset++] = x; glPositionData[offset++] = y;
3722
- glPositionData[offset++] = sizeX; glPositionData[offset++] = -sizeY;
3723
- glPositionData[offset++] = uv1X; glPositionData[offset++] = uv1Y;
3724
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3840
+ glPositionData[offset++] = x + cX - sY;
3841
+ glPositionData[offset++] = y - cY - sX;
3842
+ glPositionData[offset++] = uv1X; glPositionData[offset++] = uv1Y;
3843
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3725
3844
 
3726
3845
  // vertex 1
3727
- glPositionData[offset++] = angle;
3728
- glPositionData[offset++] = x; glPositionData[offset++] = y;
3729
- glPositionData[offset++] = sizeX; glPositionData[offset++] = sizeY;
3730
- glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
3731
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3846
+ glPositionData[offset++] = x + cX + sY;
3847
+ glPositionData[offset++] = y + cY - sX;
3848
+ glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
3849
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3732
3850
  }
3733
3851
 
3734
3852
  ///////////////////////////////////////////////////////////////////////////////
@@ -3760,6 +3878,6 @@ gl_LINK_STATUS = 35714,
3760
3878
 
3761
3879
  // constants for batch rendering
3762
3880
  gl_VERTICES_PER_QUAD = 6,
3763
- gl_INDICIES_PER_VERT = 9,
3881
+ gl_INDICIES_PER_VERT = 6,
3764
3882
  gl_MAX_BATCH = 1<<16,
3765
- gl_VERTEX_BYTE_STRIDE = 4 + (4 * 2) * 3 + (4) * 2; // float + vec2 * 3 + (char * 4) * 2
3883
+ gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2; // vec2 * 2 + (char * 4) * 2