littlejsengine 1.1.26 → 1.2.9

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 (40) hide show
  1. package/README.md +7 -9
  2. package/build.bat +1 -1
  3. package/engine/engine.all.js +369 -377
  4. package/engine/engine.all.min.js +1 -1
  5. package/engine/engine.all.release.js +338 -147
  6. package/engine/engine.js +7 -7
  7. package/engine/engineAudio.js +2 -2
  8. package/engine/engineBuild.bat +9 -1
  9. package/engine/engineDebug.js +32 -231
  10. package/engine/engineDraw.js +130 -20
  11. package/engine/engineFont.png +0 -0
  12. package/engine/engineInput.js +32 -9
  13. package/engine/engineMedals.js +7 -4
  14. package/engine/engineObject.js +30 -11
  15. package/engine/engineParticles.js +5 -3
  16. package/engine/engineSettings.js +5 -0
  17. package/engine/engineTileLayer.js +17 -14
  18. package/engine/engineUtilities.js +50 -18
  19. package/engine/engineWebGL.js +53 -59
  20. package/engine/index.d.ts +1644 -0
  21. package/examples/breakout/game.js +9 -7
  22. package/examples/breakout/gameObjects.js +3 -3
  23. package/examples/breakout/index.html +3 -3
  24. package/examples/particles/index.html +366 -0
  25. package/examples/particles/tiles.png +0 -0
  26. package/examples/platformer/game.js +5 -5
  27. package/examples/platformer/gameEffects.js +10 -10
  28. package/examples/platformer/gameLevel.js +3 -3
  29. package/examples/platformer/gameObjects.js +3 -3
  30. package/examples/platformer/gamePlayer.js +11 -6
  31. package/examples/platformer/index.html +6 -6
  32. package/examples/platformer/tiles.png +0 -0
  33. package/examples/puzzle/game.js +3 -3
  34. package/examples/puzzle/index.html +2 -2
  35. package/examples/screenshot.jpg +0 -0
  36. package/examples/stress/index.html +4 -4
  37. package/game.js +4 -7
  38. package/index.html +13 -13
  39. package/package.json +8 -1
  40. package/engine/engineBuildSetup.bat +0 -6
@@ -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
 
@@ -344,7 +344,13 @@ class Vector2
344
344
  /** Returns true if this vector is within the bounds of an array size passed in
345
345
  * @param {Vector2} arraySize
346
346
  * @return {Boolean} */
347
- arrayCheck(arraySize) { return this.x >= 0 && this.y >= 0 && this.x < arraySize.x && this.y < arraySize.y; }
347
+ arrayCheck(arraySize) { return this.x >= 0 && this.y >= 0 && this.x < arraySize.x && this.y < arraySize.y; }
348
+
349
+ /** Returns this vector expressed as a string
350
+ * @param {float} digits - precision to display
351
+ * @return {String} */
352
+ toString(digits=3)
353
+ { return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`; }
348
354
  }
349
355
 
350
356
  ///////////////////////////////////////////////////////////////////////////////
@@ -415,7 +421,7 @@ class Color
415
421
  * @return {Color} */
416
422
  lerp(c, p) { return this.add(c.subtract(this).scale(clamp(p))); }
417
423
 
418
- /** Sets this color given a hue, saturation, lightness , and alpha
424
+ /** Sets this color given a hue, saturation, lightness, and alpha
419
425
  * @param {Number} [hue=0]
420
426
  * @param {Number} [saturation=0]
421
427
  * @param {Number} [lightness=1]
@@ -451,21 +457,43 @@ class Color
451
457
  ).clamp();
452
458
  }
453
459
 
454
- /** Returns this color expressed as an rgba string
460
+ /** Returns this color expressed as an CSS color value
455
461
  * @return {String} */
456
- rgba()
462
+ toString()
457
463
  {
458
464
  ASSERT(this.r>=0 && this.r<=1 && this.g>=0 && this.g<=1 && this.b>=0 && this.b<=1 && this.a>=0 && this.a<=1);
459
465
  return `rgb(${this.r*255|0},${this.g*255|0},${this.b*255|0},${this.a})`;
460
466
  }
461
467
 
462
- /** Returns this color expressed as 32 bit integer value
468
+ /** Returns this color expressed as 32 bit integer RGBA value
463
469
  * @return {Number} */
464
470
  rgbaInt()
465
471
  {
466
472
  ASSERT(this.r>=0 && this.r<=1 && this.g>=0 && this.g<=1 && this.b>=0 && this.b<=1 && this.a>=0 && this.a<=1);
467
473
  return (this.r*255|0) + (this.g*255<<8) + (this.b*255<<16) + (this.a*255<<24);
468
474
  }
475
+
476
+ /** Returns this color expressed as a hex code
477
+ * @return {String} */
478
+ hex()
479
+ {
480
+ const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
481
+ return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b);
482
+ }
483
+
484
+ /** Set this color from a hex code
485
+ * @param {String} hex - html hex code
486
+ * @return {Color} */
487
+ setHex(hex)
488
+ {
489
+ const fromHex = (a)=> parseInt(hex.slice(a,a+2), 16)/255;
490
+ this.r = fromHex(1);
491
+ this.g = fromHex(3),
492
+ this.b = fromHex(5);
493
+ this.a = 1;
494
+ ASSERT(this.r>=0 && this.r<=1 && this.g>=0 && this.g<=1 && this.b>=0 && this.b<=1);
495
+ return this;
496
+ }
469
497
  }
470
498
 
471
499
  ///////////////////////////////////////////////////////////////////////////////
@@ -502,7 +530,7 @@ class Timer
502
530
 
503
531
  /** Returns true if set and elapsed
504
532
  * @return {Boolean} */
505
- elapsed() { return time > this.time; }
533
+ elapsed() { return time > this.time; }
506
534
 
507
535
  /** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
508
536
  * @return {Number} */
@@ -510,7 +538,11 @@ class Timer
510
538
 
511
539
  /** Get percentage elapsed based on time it was set to, returns 0 if not set
512
540
  * @return {Number} */
513
- getPercent() { return this.isSet()? percent(this.time - time, 0, this.setTime) : 0; }
541
+ getPercent() { return this.isSet()? percent(this.time - time, this.setTime, 0) : 0; }
542
+
543
+ /** Returns this timer expressed as a string
544
+ * @return {String} */
545
+ toString() { return this.unset() ? 'unset' : Math.abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ); }
514
546
  }
515
547
  /**
516
548
  * LittleJS Engine Settings
@@ -659,6 +691,11 @@ let inputWASDEmulateDirection = 1;
659
691
  * @memberof Settings */
660
692
  let touchGamepadEnable = 0;
661
693
 
694
+ /** True if touch gamepad should be analog stick or false to use if 8 way dpad
695
+ * @default
696
+ * @memberof Settings */
697
+ let touchGamepadAnalog = 1;
698
+
662
699
  /** Size of virutal gamepad for touch devices in pixels
663
700
  * @default
664
701
  * @memberof Settings */
@@ -748,7 +785,7 @@ let medalDisplayIconSize = 50;
748
785
  const engineName = 'LittleJS';
749
786
 
750
787
  /** Version of engine */
751
- const engineVersion = '1.1.26';
788
+ const engineVersion = '1.2.8';
752
789
 
753
790
  /** Frames per second to update objects
754
791
  * @default */
@@ -785,7 +822,7 @@ let averageFPS, drawCount;
785
822
  // css text used for elements created by engine
786
823
  const styleBody = 'margin:0;overflow:hidden;background:#000' +
787
824
  ';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%)';
825
+ const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
789
826
 
790
827
  ///////////////////////////////////////////////////////////////////////////////
791
828
 
@@ -810,7 +847,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
810
847
  document.body.style = styleBody;
811
848
  document.body.appendChild(mainCanvas = document.createElement('canvas'));
812
849
  mainContext = mainCanvas.getContext('2d');
813
- mainCanvas.style = canvaStyle;
850
+ mainCanvas.style = styleCanvas;
814
851
 
815
852
  // init stuff and start engine
816
853
  debugInit();
@@ -819,7 +856,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
819
856
  // create overlay canvas for hud to appear above gl canvas
820
857
  document.body.appendChild(overlayCanvas = document.createElement('canvas'));
821
858
  overlayContext = overlayCanvas.getContext('2d');
822
- overlayCanvas.style = canvaStyle;
859
+ overlayCanvas.style = styleCanvas;
823
860
 
824
861
  gameInit();
825
862
  touchGamepadCreate();
@@ -829,13 +866,11 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
829
866
  // main update loop
830
867
  const engineUpdate = (frameTimeMS=0)=>
831
868
  {
832
- requestAnimationFrame(engineUpdate);
833
-
834
869
  // update time keeping
835
870
  let frameTimeDeltaMS = frameTimeMS - frameTimeLastMS;
836
871
  frameTimeLastMS = frameTimeMS;
837
872
  if (debug || showWatermark)
838
- averageFPS = lerp(.05, 1e3/(frameTimeDeltaMS||1), averageFPS || 0);
873
+ averageFPS = lerp(.05, averageFPS || 0, 1e3/(frameTimeDeltaMS||1));
839
874
  if (debug)
840
875
  frameTimeDeltaMS *= keyIsDown(107) ? 5 : keyIsDown(109) ? .2 : 1; // +/- to speed/slow time
841
876
  timeReal += frameTimeDeltaMS / 1e3;
@@ -929,6 +964,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
929
964
  overlayContext.fillText(text, mainCanvas.width-2, 2);
930
965
  drawCount = 0;
931
966
  }
967
+
968
+ requestAnimationFrame(engineUpdate);
932
969
  }
933
970
 
934
971
  // set tile image source to load the image and start the engine
@@ -1042,11 +1079,11 @@ function engineObjectsCallback(pos, size, callbackFunction, objects=engineObject
1042
1079
  class EngineObject
1043
1080
  {
1044
1081
  /** 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
1082
+ * @param {Vector2} [position=new Vector2()] - World space position of the object
1046
1083
  * @param {Vector2} [size=objectDefaultSize] - World space size of the object
1047
1084
  * @param {Number} [tileIndex=-1] - Tile to use to render object, untextured if -1
1048
1085
  * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
1049
- * @param {Number} [angle=0] - Angle to rotate the object
1086
+ * @param {Number} [angle=0] - Angle the object is rotated by
1050
1087
  * @param {Color} [color] - Color to apply to tile when rendered
1051
1088
  * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
1052
1089
  */
@@ -1087,12 +1124,15 @@ class EngineObject
1087
1124
  this.gravityScale = 1;
1088
1125
  /** @property {Number} [renderOrder=0] - Objects are sorted by render order */
1089
1126
  this.renderOrder = renderOrder;
1127
+ /** @property {Vector2} [velocity=new Vector2()] - Velocity of the object */
1128
+ this.velocity = new Vector2();
1129
+ /** @property {Number} [angleVelocity=0] - Angular velocity of the object */
1130
+ this.angleVelocity = 0;
1090
1131
 
1091
1132
  // init other internal object stuff
1092
1133
  this.spawnTime = time;
1093
- this.velocity = vec2(this.collideSolidObjects = this.angleVelocity = 0);
1094
- this.collideTiles = 1;
1095
1134
  this.children = [];
1135
+ this.collideTiles = 1;
1096
1136
 
1097
1137
  // add to list of objects
1098
1138
  engineObjects.push(this);
@@ -1111,8 +1151,8 @@ class EngineObject
1111
1151
  }
1112
1152
 
1113
1153
  // 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);
1154
+ this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
1155
+ this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
1116
1156
 
1117
1157
  // apply physics
1118
1158
  const oldPos = this.pos.copy();
@@ -1202,8 +1242,8 @@ class EngineObject
1202
1242
 
1203
1243
  // lerp betwen elastic or inelastic based on elasticity
1204
1244
  const elasticity = max(this.elasticity, o.elasticity);
1205
- this.velocity.y = lerp(elasticity, elastic0, inelastic);
1206
- o.velocity.y = lerp(elasticity, elastic1, inelastic);
1245
+ this.velocity.y = lerp(elasticity, inelastic, elastic0);
1246
+ o.velocity.y = lerp(elasticity, inelastic, elastic1);
1207
1247
  }
1208
1248
  }
1209
1249
  if (!smallStepUp && (isBlockedX || !isBlockedY)) // resolve x collision
@@ -1223,8 +1263,8 @@ class EngineObject
1223
1263
 
1224
1264
  // lerp betwen elastic or inelastic based on elasticity
1225
1265
  const elasticity = max(this.elasticity, o.elasticity);
1226
- this.velocity.x = lerp(elasticity, elastic0, inelastic);
1227
- o.velocity.x = lerp(elasticity, elastic1, inelastic);
1266
+ this.velocity.x = lerp(elasticity, inelastic, elastic0);
1267
+ o.velocity.x = lerp(elasticity, inelastic, elastic1);
1228
1268
  }
1229
1269
  else // bounce if other object is fixed
1230
1270
  this.velocity.x *= -this.elasticity;
@@ -1254,7 +1294,7 @@ class EngineObject
1254
1294
 
1255
1295
  // adjust next velocity to settle on ground
1256
1296
  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)
1297
+ if (o < 0 && o > this.damping * this.velocity.y + gravity * this.gravityScale)
1258
1298
  this.velocity.y = this.damping ? (o - gravity * this.gravityScale) / this.damping : 0;
1259
1299
 
1260
1300
  // move to previous position
@@ -1359,6 +1399,22 @@ class EngineObject
1359
1399
  this.isSolid = isSolid;
1360
1400
  this.collideTiles = collideTiles;
1361
1401
  }
1402
+
1403
+ toString()
1404
+ {
1405
+ let text = 'type = ' + this.constructor.name;
1406
+ if (this.pos.x || this.pos.y)
1407
+ text += '\npos = ' + this.pos;
1408
+ if (this.velocity.x || this.velocity.y)
1409
+ text += '\nvelocity = ' + this.velocity;
1410
+ if (this.size.x || this.size.y)
1411
+ text += '\nsize = ' + this.size;
1412
+ if (this.angle)
1413
+ text += '\nangle = ' + this.angle.toFixed(3);
1414
+ if (this.color)
1415
+ text += '\ncolor = ' + this.color;
1416
+ return text;
1417
+ }
1362
1418
  }
1363
1419
  /**
1364
1420
  * LittleJS Drawing System
@@ -1472,7 +1528,7 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, col
1472
1528
  if (tileIndex < 0)
1473
1529
  {
1474
1530
  // if negative tile index, force untextured
1475
- context.fillStyle = color.rgba();
1531
+ context.fillStyle = color;
1476
1532
  context.fillRect(-.5, -.5, 1, 1);
1477
1533
  }
1478
1534
  else
@@ -1565,7 +1621,20 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainCont
1565
1621
  context.restore();
1566
1622
  }
1567
1623
 
1568
- /** Draw text on overlay canvas in world space
1624
+ /** Enable normal or additive blend mode
1625
+ * @param {Boolean} [additive=0]
1626
+ * @param {Boolean} [useWebGL=glEnable]
1627
+ * @memberof Draw */
1628
+ function setBlendMode(additive, useWebGL=glEnable)
1629
+ {
1630
+ if (glEnable && useWebGL)
1631
+ glSetBlendMode(additive);
1632
+ else
1633
+ mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
1634
+ }
1635
+
1636
+ /** Draw text on overlay canvas in screen space
1637
+ * Automatically splits new lines into rows
1569
1638
  * @param {String} text
1570
1639
  * @param {Vector2} pos
1571
1640
  * @param {Number} [size=1]
@@ -1574,32 +1643,129 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainCont
1574
1643
  * @param {Color} [lineColor=new Color(0,0,0)]
1575
1644
  * @param {String} [textAlign='center']
1576
1645
  * @memberof Draw */
1577
- function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault)
1646
+ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault)
1578
1647
  {
1579
- pos = worldToScreen(pos);
1580
- overlayContext.font = size*cameraScale + 'px '+ font;
1648
+ overlayContext.fillStyle = color;
1649
+ overlayContext.lineWidth = lineWidth *= cameraScale;
1650
+ overlayContext.strokeStyle = lineColor;
1581
1651
  overlayContext.textAlign = textAlign;
1652
+ overlayContext.font = size + 'px '+ font;
1582
1653
  overlayContext.textBaseline = 'middle';
1583
- if (lineWidth)
1654
+
1655
+ pos = pos.copy();
1656
+ text.split('\n').forEach(line=>
1584
1657
  {
1585
- overlayContext.lineWidth = lineWidth*cameraScale;
1586
- overlayContext.strokeStyle = lineColor.rgba();
1587
- overlayContext.strokeText(text, pos.x, pos.y);
1588
- }
1589
- overlayContext.fillStyle = color.rgba();
1590
- overlayContext.fillText(text, pos.x, pos.y);
1658
+ lineWidth && overlayContext.strokeText(line, pos.x, pos.y);
1659
+ overlayContext.fillText(line, pos.x, pos.y);
1660
+ pos.y += size;
1661
+ });
1591
1662
  }
1592
1663
 
1593
- /** Enable normal or additive blend mode
1594
- * @param {Boolean} [additive=0]
1595
- * @param {Boolean} [useWebGL=glEnable]
1664
+ /** Draw text on overlay canvas in world space
1665
+ * Automatically splits new lines into rows
1666
+ * @param {String} text
1667
+ * @param {Vector2} pos
1668
+ * @param {Number} [size=1]
1669
+ * @param {Color} [color=new Color(1,1,1)]
1670
+ * @param {Number} [lineWidth=0]
1671
+ * @param {Color} [lineColor=new Color(0,0,0)]
1672
+ * @param {String} [textAlign='center']
1596
1673
  * @memberof Draw */
1597
- function setBlendMode(additive, useWebGL=glEnable)
1674
+ function drawText(text, pos, size=1, color, lineWidth, lineColor, textAlign, font)
1598
1675
  {
1599
- if (glEnable && useWebGL)
1600
- glSetBlendMode(additive);
1601
- else
1602
- mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
1676
+ drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth, lineColor, textAlign, font);
1677
+ }
1678
+
1679
+ ///////////////////////////////////////////////////////////////////////////////
1680
+
1681
+ /**
1682
+ * Font Image Object - Draw text on a 2D canvas by using characters in an image
1683
+ * <br> - 96 characters (from space to tilde) are stored in an image
1684
+ * <br> - Uses a default 8x8 font if none is supplied
1685
+ * <br> - You can also use fonts from the main tile sheet
1686
+ * @example
1687
+ * // use built in font
1688
+ * const font = new ImageFont;
1689
+ *
1690
+ * // draw text
1691
+ * font.drawTextScreen("LittleJS\nHello World!", vec2(200, 50));
1692
+ */
1693
+
1694
+ let engineFontImage;
1695
+
1696
+ class FontImage
1697
+ {
1698
+ /** Create an image font
1699
+ * @param {Image} [image] - The image the font is stored in, if undefined the default font is used
1700
+ * @param {Vector2} [tileSize=vec2(8)] - The size of the font source tiles
1701
+ * @param {Vector2} [paddingSize=vec2(0,1)] - How much extra space to add between characters
1702
+ * @param {Number} [startTileIndex=0] - Tile index in image where font starts
1703
+ * @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to
1704
+ */
1705
+ constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), startTileIndex=0, context=overlayContext)
1706
+ {
1707
+ if (!image && !engineFontImage)
1708
+ {
1709
+ // load default font image
1710
+ engineFontImage = new Image();
1711
+ engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
1712
+ }
1713
+
1714
+ this.image = image || engineFontImage;
1715
+ this.tileSize = tileSize;
1716
+ this.paddingSize = paddingSize;
1717
+ this.startTileIndex = startTileIndex;
1718
+ this.context = context;
1719
+ }
1720
+
1721
+ /** Draw text in screen space using the image font
1722
+ * @param {String} text
1723
+ * @param {Vector2} pos
1724
+ * @param {Number} [scale=4]
1725
+ * @param {Boolean} [center]
1726
+ */
1727
+ drawTextScreen(text, pos, scale=4, center)
1728
+ {
1729
+ const context = this.context;
1730
+ context.save();
1731
+ context.imageSmoothingEnabled = !cavasPixelated;
1732
+
1733
+ const size = this.tileSize;
1734
+ const drawSize = size.add(this.paddingSize).scale(scale);
1735
+ const cols = this.image.width / this.tileSize.x |0;
1736
+ text.split('\n').forEach((line, i)=>
1737
+ {
1738
+ const centerOffset = center ? line.length * size.x * scale / 2 |0 : 0;
1739
+ for(let j=line.length; j--;)
1740
+ {
1741
+ // draw each character
1742
+ let charCode = line[j].charCodeAt();
1743
+ if (charCode < 32 || charCode > 127)
1744
+ charCode = 127; // unknown character
1745
+
1746
+ // get the character source location and draw it
1747
+ const tile = this.startTileIndex + charCode - 32;
1748
+ const x = tile % cols;
1749
+ const y = tile / cols |0;
1750
+ const drawPos = pos.add(vec2(j,i).multiply(drawSize));
1751
+ context.drawImage(this.image, x * size.x, y * size.y, size.x, size.y,
1752
+ drawPos.x - centerOffset, drawPos.y, size.x * scale, size.y * scale);
1753
+ }
1754
+ });
1755
+
1756
+ context.restore();
1757
+ }
1758
+
1759
+ /** Draw text in world space using the image font
1760
+ * @param {String} text
1761
+ * @param {Vector2} pos
1762
+ * @param {Number} [scale=.25]
1763
+ * @param {Boolean} [center]
1764
+ */
1765
+ drawText(text, pos, scale=1, center)
1766
+ {
1767
+ this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center);
1768
+ }
1603
1769
  }
1604
1770
 
1605
1771
  ///////////////////////////////////////////////////////////////////////////////
@@ -1790,7 +1956,7 @@ const mouseToScreen = (mousePos)=>
1790
1956
 
1791
1957
  const rect = mainCanvas.getBoundingClientRect();
1792
1958
  return mainCanvasSize.multiply(
1793
- vec2(percent(mousePos.x, rect.right, rect.left), percent(mousePos.y, rect.bottom, rect.top)));
1959
+ vec2(percent(mousePos.x, rect.left, rect.right), percent(mousePos.y, rect.top, rect.bottom)));
1794
1960
  }
1795
1961
 
1796
1962
  ///////////////////////////////////////////////////////////////////////////////
@@ -1831,8 +1997,8 @@ function gamepadsUpdate()
1831
1997
  // read clamp dead zone of analog sticks
1832
1998
  const deadZone = .3, deadZoneMax = .8;
1833
1999
  const applyDeadZone = (v)=>
1834
- v > deadZone ? percent( v, deadZoneMax, deadZone) :
1835
- v < -deadZone ? -percent(-v, deadZoneMax, deadZone) : 0;
2000
+ v > deadZone ? percent( v, deadZone, deadZoneMax) :
2001
+ v < -deadZone ? -percent(-v, deadZone, deadZoneMax) : 0;
1836
2002
 
1837
2003
  // read analog sticks
1838
2004
  for (let j = 0; j < gamepad.axes.length-1; j+=2)
@@ -1958,7 +2124,14 @@ function touchGamepadCreate()
1958
2124
  if (touchPos.distance(stickCenter) < touchGamepadSize)
1959
2125
  {
1960
2126
  // virtual analog stick
1961
- touchGamepadStick = touchPos.subtract(stickCenter).clampLength(touchGamepadSize/2).scale(2/touchGamepadSize);
2127
+ if (touchGamepadAnalog)
2128
+ touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
2129
+ else
2130
+ {
2131
+ // 8 way dpad
2132
+ const angle = touchPos.subtract(stickCenter).angle();
2133
+ touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
2134
+ }
1962
2135
  }
1963
2136
  else if (touchPos.distance(buttonCenter) < touchGamepadSize)
1964
2137
  {
@@ -1982,7 +2155,7 @@ function touchGamepadRender()
1982
2155
  return;
1983
2156
 
1984
2157
  // fade off when not touching or paused
1985
- const alpha = percent(touchGamepadTimer.get(), 3, 4);
2158
+ const alpha = percent(touchGamepadTimer.get(), 4, 3);
1986
2159
  if (!alpha || paused)
1987
2160
  return;
1988
2161
 
@@ -1993,12 +2166,28 @@ function touchGamepadRender()
1993
2166
  overlayContext.lineWidth = 3;
1994
2167
 
1995
2168
  // draw left analog stick
1996
- overlayContext.fillStyle = gamepadStick(0).length() > 0 ? '#fff' : '#000';
2169
+ overlayContext.fillStyle = touchGamepadStick.lengthSquared() > 0 ? '#fff' : '#000';
1997
2170
  overlayContext.beginPath();
1998
- overlayContext.arc(touchGamepadSize, mainCanvasSize.y-touchGamepadSize, touchGamepadSize/2, 0,9);
1999
- overlayContext.fill();
2000
- overlayContext.stroke();
2001
2171
 
2172
+ const leftCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
2173
+ if (touchGamepadAnalog)
2174
+ {
2175
+ overlayContext.arc(leftCenter.x, leftCenter.y, touchGamepadSize/2, 0, 9);
2176
+ overlayContext.fill();
2177
+ overlayContext.stroke();
2178
+ }
2179
+ else // draw cross shaped gamepad
2180
+ {
2181
+ for(let i=10; i--;)
2182
+ {
2183
+ const angle = i*PI/4;
2184
+ overlayContext.arc(leftCenter.x, leftCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
2185
+ i%2 && overlayContext.arc(leftCenter.x, leftCenter.y, touchGamepadSize*.33, angle, angle);
2186
+ i==1 && overlayContext.fill();
2187
+ }
2188
+ overlayContext.stroke();
2189
+ }
2190
+
2002
2191
  // draw right face buttons
2003
2192
  const rightCenter = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
2004
2193
  for (let i=4; i--;)
@@ -2085,7 +2274,7 @@ class Sound
2085
2274
  return; // out of range
2086
2275
 
2087
2276
  // attenuate volume by distance
2088
- volume *= percent(lengthSquared**.5, range*this.taper, range);
2277
+ volume *= percent(lengthSquared**.5, range, range*this.taper);
2089
2278
  }
2090
2279
 
2091
2280
  // get pan from screen space coords
@@ -2267,7 +2456,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
2267
2456
  // connect source to gain
2268
2457
  (
2269
2458
  window.StereoPannerNode ? // create pan node if possible
2270
- source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, 1, -1)}))
2459
+ source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)}))
2271
2460
  : source
2272
2461
  )
2273
2462
  .connect(gainNode);
@@ -2639,14 +2828,14 @@ class TileLayerData
2639
2828
  */
2640
2829
  class TileLayer extends EngineObject
2641
2830
  {
2642
- /** 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
2648
- */
2649
- constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1), renderOrder=0)
2831
+ /** Create a tile layer object
2832
+ * @param {Vector2} [position=new Vector2()] - World space position
2833
+ * @param {Vector2} [size=tileCollisionSize] - World space size
2834
+ * @param {Vector2} [tileSize=tileSizeDefault] - Size of tiles in source pixels
2835
+ * @param {Vector2} [scale=new Vector2(1,1)] - How much to scale this layer when rendered
2836
+ * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
2837
+ */
2838
+ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1), renderOrder=0)
2650
2839
  {
2651
2840
  super(pos, size, -1, tileSize, 0, undefined, renderOrder);
2652
2841
 
@@ -2704,17 +2893,19 @@ class TileLayer extends EngineObject
2704
2893
  );
2705
2894
  }
2706
2895
 
2707
- /** Draw all the tile data to an offscreen canvas */
2896
+ /** Draw all the tile data to an offscreen canvas
2897
+ * - This may be slow in some browsers
2898
+ */
2708
2899
  redraw()
2709
2900
  {
2710
- this.redrawStart();
2901
+ this.redrawStart(1);
2711
2902
  this.drawAllTileData();
2712
2903
  this.redrawEnd();
2713
2904
  }
2714
2905
 
2715
2906
  /** Call to start the redraw process
2716
- * @param {Boolean} [clear=1] - Should it clear the canvas before drawing */
2717
- redrawStart(clear = 1)
2907
+ * @param {Boolean} [clear=0] - Should it clear the canvas before drawing */
2908
+ redrawStart(clear = 0)
2718
2909
  {
2719
2910
  if (clear)
2720
2911
  {
@@ -2804,7 +2995,7 @@ class TileLayer extends EngineObject
2804
2995
  if (tileIndex < 0)
2805
2996
  {
2806
2997
  // untextured
2807
- context.fillStyle = color.rgba();
2998
+ context.fillStyle = color;
2808
2999
  context.fillRect(-.5, -.5, 1, 1);
2809
3000
  }
2810
3001
  else
@@ -2823,7 +3014,8 @@ class TileLayer extends EngineObject
2823
3014
  * @param {Vector2} [size=new Vector2(1,1)]
2824
3015
  * @param {Color} [color=new Color(1,1,1)]
2825
3016
  * @param {Number} [angle=0] */
2826
- drawRect(pos, size, color, angle) { this.drawTile(pos, size, -1, 0, color, angle); }
3017
+ drawRect(pos, size, color, angle)
3018
+ { this.drawTile(pos, size, -1, 0, color, angle); }
2827
3019
  }
2828
3020
  /*
2829
3021
  LittleJS Particle System
@@ -2842,9 +3034,9 @@ class TileLayer extends EngineObject
2842
3034
  * let pos = vec2(2,3);
2843
3035
  * let particleEmiter = new ParticleEmitter
2844
3036
  * (
2845
- * pos, 1, 0, 500, PI, // pos, emitSize, emitTime, emitRate, emiteCone
3037
+ * pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emiteCone
2846
3038
  * 0, vec2(16), // tileIndex, tileSize
2847
- * new Color, new Color(0,0,0), // colorStartA, colorStartB
3039
+ * new Color(1,1,1), new Color(0,0,0), // colorStartA, colorStartB
2848
3040
  * new Color(1,1,1,0), new Color(0,0,0,0), // colorEndA, colorEndB
2849
3041
  * 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
2850
3042
  * .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
@@ -2855,6 +3047,7 @@ class ParticleEmitter extends EngineObject
2855
3047
  {
2856
3048
  /** Create a particle system with the given settings
2857
3049
  * @param {Vector2} position - World space position of the emitter
3050
+ * @param {Number} [angle=0] - Angle to emit the particles
2858
3051
  * @param {Number} [emitSize=0] - World space size of the emitter (float for circle diameter, vec2 for rect)
2859
3052
  * @param {Number} [emitTime=0] - How long to stay alive (0 is forever)
2860
3053
  * @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0
@@ -2884,6 +3077,7 @@ class ParticleEmitter extends EngineObject
2884
3077
  constructor
2885
3078
  (
2886
3079
  pos,
3080
+ angle,
2887
3081
  emitSize = 0,
2888
3082
  emitTime = 0,
2889
3083
  emitRate = 100,
@@ -2911,7 +3105,7 @@ class ParticleEmitter extends EngineObject
2911
3105
  renderOrder = additive ? 1e9 : 0
2912
3106
  )
2913
3107
  {
2914
- super(pos, new Vector2, tileIndex, tileSize, 0, undefined, renderOrder);
3108
+ super(pos, new Vector2, tileIndex, tileSize, angle, undefined, renderOrder);
2915
3109
 
2916
3110
  // emitter settings
2917
3111
  /** @property {Number} - World space size of the emitter (float for circle diameter, vec2 for rect) */
@@ -3144,7 +3338,7 @@ function medalsInit(saveName)
3144
3338
  {
3145
3339
  // check if medals are unlocked
3146
3340
  medalsSaveName = saveName;
3147
- debugMedals || medals.forEach(medal=> medal.unlocked = 1 || localStorage[medal.storageKey()]);
3341
+ debugMedals || medals.forEach(medal=> localStorage[medal.storageKey()]);
3148
3342
  }
3149
3343
 
3150
3344
  /**
@@ -3333,6 +3527,9 @@ class Newgrounds
3333
3527
  const scoreboardResult = this.call('ScoreBoard.getBoards');
3334
3528
  this.scoreboards = scoreboardResult ? scoreboardResult.result.data.scoreboards : [];
3335
3529
  debugMedals && console.log(this.scoreboards);
3530
+
3531
+ const keepAliveMS = 5 * 60 * 1e3;
3532
+ setInterval(()=>this.call('Gateway.ping', 0, 1), keepAliveMS);
3336
3533
  }
3337
3534
 
3338
3535
  /** Send message to unlock a medal by id
@@ -3344,9 +3541,6 @@ class Newgrounds
3344
3541
  * @param {Number} value - The score value */
3345
3542
  postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, 1); }
3346
3543
 
3347
- /** Send message to log a view */
3348
- logView() { return this.call('App.logView', {'host':this.host}, 1); }
3349
-
3350
3544
  /** Get scores from a scoreboard
3351
3545
  * @param {Number} id - The scoreboard id
3352
3546
  * @param {String} [user=0] - A user's id or name
@@ -3358,6 +3552,9 @@ class Newgrounds
3358
3552
  getScores(id, user=0, social=0, skip=0, limit=10)
3359
3553
  { return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
3360
3554
 
3555
+ /** Send message to log a view */
3556
+ logView() { return this.call('App.logView', {'host':this.host}, 1); }
3557
+
3361
3558
  /** Send a message to call a component of the Newgrounds API
3362
3559
  * @param {String} component - Name of the component
3363
3560
  * @param {Object} [parameters=0] - Parameters to use for call
@@ -3445,8 +3642,8 @@ function glInit()
3445
3642
 
3446
3643
  // create the canvas and tile texture
3447
3644
  glCanvas = document.createElement('canvas');
3448
- glContext = glCanvas.getContext('webgl');
3449
- glCanvas.style = canvaStyle;
3645
+ glContext = glCanvas.getContext('webgl', {antialias: false});
3646
+ glCanvas.style = styleCanvas;
3450
3647
  glTileTexture = glCreateTexture(tileImage);
3451
3648
 
3452
3649
  // some browsers are much faster without copying the gl buffer so we just overlay it instead
@@ -3454,25 +3651,24 @@ function glInit()
3454
3651
 
3455
3652
  // setup vertex and fragment shaders
3456
3653
  glShader = glCreateProgram(
3457
- 'precision highp float;'+ // use highp for better accuracy, lowp for better perf
3458
- '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
3462
- 'varying vec2 v;'+ // return uv
3463
- 'varying vec4 d,e;'+ // return color, additiveColor
3464
- '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
3467
- '}' // end of shader
3654
+ 'precision highp float;'+ // use highp for better accuracy
3655
+ 'uniform mat4 m;'+ // transform matrix
3656
+ 'attribute vec2 p,t;'+ // position, uv
3657
+ 'attribute vec4 c,a;'+ // color, additiveColor
3658
+ 'varying vec2 v;'+ // return uv
3659
+ 'varying vec4 d,e;'+ // return color, additiveColor
3660
+ 'void main(){'+ // shader entry point
3661
+ 'gl_Position=m*vec4(p,1,1);'+ // transform position
3662
+ 'v=t;d=c;e=a;'+ // pass stuff to fragment shader
3663
+ '}' // end of shader
3468
3664
  ,
3469
- 'precision highp float;'+ // use highp for better accuracy, lowp for better perf
3470
- 'varying vec2 v;'+ // uv
3471
- 'varying vec4 d,e;'+ // color, additiveColor
3472
- 'uniform sampler2D j;'+ // texture
3473
- 'void main(){'+ // shader entry point
3474
- 'gl_FragColor=texture2D(j,v)*d+e;'+ // modulate texture by color plus additive
3475
- '}' // end of shader
3665
+ 'precision highp float;'+ // use highp for better accuracy
3666
+ 'varying vec2 v;'+ // uv
3667
+ 'varying vec4 d,e;'+ // color, additiveColor
3668
+ 'uniform sampler2D s;'+ // texture
3669
+ 'void main(){'+ // shader entry point
3670
+ 'gl_FragColor=texture2D(s,v)*d+e;'+ // modulate texture by color plus additive
3671
+ '}' // end of shader
3476
3672
  );
3477
3673
 
3478
3674
  // init buffers
@@ -3490,12 +3686,10 @@ function glInit()
3490
3686
  glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
3491
3687
  offset += size*typeSize;
3492
3688
  }
3493
- initVertexAttribArray('a', gl_FLOAT, 4, 1); // angle
3494
3689
  initVertexAttribArray('p', gl_FLOAT, 4, 2); // position
3495
- initVertexAttribArray('s', gl_FLOAT, 4, 2); // size
3496
3690
  initVertexAttribArray('t', gl_FLOAT, 4, 2); // texture coords
3497
3691
  initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
3498
- initVertexAttribArray('b', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
3692
+ initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
3499
3693
  }
3500
3694
 
3501
3695
  /** Set the WebGl blend mode, normally you should call setBlendMode instead
@@ -3610,6 +3804,7 @@ function glPreRender(width, height, cameraX, cameraY, cameraScale)
3610
3804
 
3611
3805
  // clear and set to same size as main canvas
3612
3806
  glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height);
3807
+ glContext.clear(gl_COLOR_BUFFER_BIT);
3613
3808
 
3614
3809
  // set up the shader
3615
3810
  glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
@@ -3656,12 +3851,10 @@ function glCopyToContext(context, forceDraw)
3656
3851
  if (!glEnable || !glBatchCount) return;
3657
3852
 
3658
3853
  glFlush();
3854
+
3855
+ // do not draw in overlay mode because the canvas is visible
3659
3856
  if (!glOverlay || forceDraw)
3660
- {
3661
- // do not draw/clear in overlay mode because the canvas is visible
3662
3857
  context.drawImage(glCanvas, 0, 0);
3663
- glContext.clear(gl_COLOR_BUFFER_BIT);
3664
- }
3665
3858
  }
3666
3859
 
3667
3860
  /** Add a sprite to the gl draw list, used by all gl draw functions
@@ -3684,51 +3877,49 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=0xffffff
3684
3877
  // flush if there is no room for more verts or if different blend mode
3685
3878
  if (glBatchCount == gl_MAX_BATCH || glBatchAdditive != glAdditive)
3686
3879
  glFlush();
3880
+
3881
+ // prepare to create the verts from size and angle
3882
+ const c = Math.cos(angle)/2, s = Math.sin(angle)/2;
3883
+ const cx = c*sizeX, cy = c*sizeY, sx = s*sizeX, sy = s*sizeY;
3687
3884
 
3688
3885
  // setup 2 triangles to form a quad
3689
3886
  let offset = glBatchCount++ * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT;
3690
3887
 
3691
3888
  // 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;
3889
+ glPositionData[offset++] = x - cx - sy;
3890
+ glPositionData[offset++] = y - cy + sx;
3891
+ glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
3892
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3697
3893
 
3698
3894
  // 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;
3895
+ glPositionData[offset++] = x + cx + sy;
3896
+ glPositionData[offset++] = y + cy - sx;
3897
+ glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
3898
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3704
3899
 
3705
3900
  // 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;
3901
+ glPositionData[offset++] = x - cx + sy;
3902
+ glPositionData[offset++] = y + cy + sx;
3903
+ glPositionData[offset++] = uv0X; glPositionData[offset++] = uv0Y;
3904
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3711
3905
 
3712
3906
  // 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;
3907
+ glPositionData[offset++] = x - cx - sy;
3908
+ glPositionData[offset++] = y - cy + sx;
3909
+ glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
3910
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3718
3911
 
3719
3912
  // 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;
3913
+ glPositionData[offset++] = x + cx - sy;
3914
+ glPositionData[offset++] = y - cy - sx;
3915
+ glPositionData[offset++] = uv1X; glPositionData[offset++] = uv1Y;
3916
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3725
3917
 
3726
3918
  // 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;
3919
+ glPositionData[offset++] = x + cx + sy;
3920
+ glPositionData[offset++] = y + cy - sx;
3921
+ glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
3922
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3732
3923
  }
3733
3924
 
3734
3925
  ///////////////////////////////////////////////////////////////////////////////
@@ -3760,6 +3951,6 @@ gl_LINK_STATUS = 35714,
3760
3951
 
3761
3952
  // constants for batch rendering
3762
3953
  gl_VERTICES_PER_QUAD = 6,
3763
- gl_INDICIES_PER_VERT = 9,
3954
+ gl_INDICIES_PER_VERT = 6,
3764
3955
  gl_MAX_BATCH = 1<<16,
3765
- gl_VERTEX_BYTE_STRIDE = 4 + (4 * 2) * 3 + (4) * 2; // float + vec2 * 3 + (char * 4) * 2
3956
+ gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2; // vec2 * 2 + (char * 4) * 2