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
@@ -159,7 +159,7 @@ const mouseToScreen = (mousePos)=>
159
159
 
160
160
  const rect = mainCanvas.getBoundingClientRect();
161
161
  return mainCanvasSize.multiply(
162
- vec2(percent(mousePos.x, rect.right, rect.left), percent(mousePos.y, rect.bottom, rect.top)));
162
+ vec2(percent(mousePos.x, rect.left, rect.right), percent(mousePos.y, rect.top, rect.bottom)));
163
163
  }
164
164
 
165
165
  ///////////////////////////////////////////////////////////////////////////////
@@ -200,8 +200,8 @@ function gamepadsUpdate()
200
200
  // read clamp dead zone of analog sticks
201
201
  const deadZone = .3, deadZoneMax = .8;
202
202
  const applyDeadZone = (v)=>
203
- v > deadZone ? percent( v, deadZoneMax, deadZone) :
204
- v < -deadZone ? -percent(-v, deadZoneMax, deadZone) : 0;
203
+ v > deadZone ? percent( v, deadZone, deadZoneMax) :
204
+ v < -deadZone ? -percent(-v, deadZone, deadZoneMax) : 0;
205
205
 
206
206
  // read analog sticks
207
207
  for (let j = 0; j < gamepad.axes.length-1; j+=2)
@@ -327,7 +327,14 @@ function touchGamepadCreate()
327
327
  if (touchPos.distance(stickCenter) < touchGamepadSize)
328
328
  {
329
329
  // virtual analog stick
330
- touchGamepadStick = touchPos.subtract(stickCenter).clampLength(touchGamepadSize/2).scale(2/touchGamepadSize);
330
+ if (touchGamepadAnalog)
331
+ touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
332
+ else
333
+ {
334
+ // 8 way dpad
335
+ const angle = touchPos.subtract(stickCenter).angle();
336
+ touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
337
+ }
331
338
  }
332
339
  else if (touchPos.distance(buttonCenter) < touchGamepadSize)
333
340
  {
@@ -351,7 +358,7 @@ function touchGamepadRender()
351
358
  return;
352
359
 
353
360
  // fade off when not touching or paused
354
- const alpha = percent(touchGamepadTimer.get(), 3, 4);
361
+ const alpha = percent(touchGamepadTimer.get(), 4, 3);
355
362
  if (!alpha || paused)
356
363
  return;
357
364
 
@@ -362,12 +369,28 @@ function touchGamepadRender()
362
369
  overlayContext.lineWidth = 3;
363
370
 
364
371
  // draw left analog stick
365
- overlayContext.fillStyle = gamepadStick(0).length() > 0 ? '#fff' : '#000';
372
+ overlayContext.fillStyle = touchGamepadStick.lengthSquared() > 0 ? '#fff' : '#000';
366
373
  overlayContext.beginPath();
367
- overlayContext.arc(touchGamepadSize, mainCanvasSize.y-touchGamepadSize, touchGamepadSize/2, 0,9);
368
- overlayContext.fill();
369
- overlayContext.stroke();
370
374
 
375
+ const leftCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
376
+ if (touchGamepadAnalog)
377
+ {
378
+ overlayContext.arc(leftCenter.x, leftCenter.y, touchGamepadSize/2, 0, 9);
379
+ overlayContext.fill();
380
+ overlayContext.stroke();
381
+ }
382
+ else // draw cross shaped gamepad
383
+ {
384
+ for(let i=10; i--;)
385
+ {
386
+ const angle = i*PI/4;
387
+ overlayContext.arc(leftCenter.x, leftCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
388
+ i%2 && overlayContext.arc(leftCenter.x, leftCenter.y, touchGamepadSize*.33, angle, angle);
389
+ i==1 && overlayContext.fill();
390
+ }
391
+ overlayContext.stroke();
392
+ }
393
+
371
394
  // draw right face buttons
372
395
  const rightCenter = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
373
396
  for (let i=4; i--;)
@@ -35,7 +35,7 @@ function medalsInit(saveName)
35
35
  {
36
36
  // check if medals are unlocked
37
37
  medalsSaveName = saveName;
38
- debugMedals || medals.forEach(medal=> medal.unlocked = 1 || localStorage[medal.storageKey()]);
38
+ debugMedals || medals.forEach(medal=> localStorage[medal.storageKey()]);
39
39
  }
40
40
 
41
41
  /**
@@ -224,6 +224,9 @@ class Newgrounds
224
224
  const scoreboardResult = this.call('ScoreBoard.getBoards');
225
225
  this.scoreboards = scoreboardResult ? scoreboardResult.result.data.scoreboards : [];
226
226
  debugMedals && console.log(this.scoreboards);
227
+
228
+ const keepAliveMS = 5 * 60 * 1e3;
229
+ setInterval(()=>this.call('Gateway.ping', 0, 1), keepAliveMS);
227
230
  }
228
231
 
229
232
  /** Send message to unlock a medal by id
@@ -235,9 +238,6 @@ class Newgrounds
235
238
  * @param {Number} value - The score value */
236
239
  postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, 1); }
237
240
 
238
- /** Send message to log a view */
239
- logView() { return this.call('App.logView', {'host':this.host}, 1); }
240
-
241
241
  /** Get scores from a scoreboard
242
242
  * @param {Number} id - The scoreboard id
243
243
  * @param {String} [user=0] - A user's id or name
@@ -249,6 +249,9 @@ class Newgrounds
249
249
  getScores(id, user=0, social=0, skip=0, limit=10)
250
250
  { return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
251
251
 
252
+ /** Send message to log a view */
253
+ logView() { return this.call('App.logView', {'host':this.host}, 1); }
254
+
252
255
  /** Send a message to call a component of the Newgrounds API
253
256
  * @param {String} component - Name of the component
254
257
  * @param {Object} [parameters=0] - Parameters to use for call
@@ -32,11 +32,11 @@
32
32
  class EngineObject
33
33
  {
34
34
  /** Create an engine object and adds it to the list of objects
35
- * @param {Vector2} [position=new Vector2(0,0)] - World space position of the object
35
+ * @param {Vector2} [position=new Vector2()] - World space position of the object
36
36
  * @param {Vector2} [size=objectDefaultSize] - World space size of the object
37
37
  * @param {Number} [tileIndex=-1] - Tile to use to render object, untextured if -1
38
38
  * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
39
- * @param {Number} [angle=0] - Angle to rotate the object
39
+ * @param {Number} [angle=0] - Angle the object is rotated by
40
40
  * @param {Color} [color] - Color to apply to tile when rendered
41
41
  * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
42
42
  */
@@ -77,12 +77,15 @@ class EngineObject
77
77
  this.gravityScale = 1;
78
78
  /** @property {Number} [renderOrder=0] - Objects are sorted by render order */
79
79
  this.renderOrder = renderOrder;
80
+ /** @property {Vector2} [velocity=new Vector2()] - Velocity of the object */
81
+ this.velocity = new Vector2();
82
+ /** @property {Number} [angleVelocity=0] - Angular velocity of the object */
83
+ this.angleVelocity = 0;
80
84
 
81
85
  // init other internal object stuff
82
86
  this.spawnTime = time;
83
- this.velocity = vec2(this.collideSolidObjects = this.angleVelocity = 0);
84
- this.collideTiles = 1;
85
87
  this.children = [];
88
+ this.collideTiles = 1;
86
89
 
87
90
  // add to list of objects
88
91
  engineObjects.push(this);
@@ -101,8 +104,8 @@ class EngineObject
101
104
  }
102
105
 
103
106
  // limit max speed to prevent missing collisions
104
- this.velocity.x = clamp(this.velocity.x, objectMaxSpeed, -objectMaxSpeed);
105
- this.velocity.y = clamp(this.velocity.y, objectMaxSpeed, -objectMaxSpeed);
107
+ this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
108
+ this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
106
109
 
107
110
  // apply physics
108
111
  const oldPos = this.pos.copy();
@@ -192,8 +195,8 @@ class EngineObject
192
195
 
193
196
  // lerp betwen elastic or inelastic based on elasticity
194
197
  const elasticity = max(this.elasticity, o.elasticity);
195
- this.velocity.y = lerp(elasticity, elastic0, inelastic);
196
- o.velocity.y = lerp(elasticity, elastic1, inelastic);
198
+ this.velocity.y = lerp(elasticity, inelastic, elastic0);
199
+ o.velocity.y = lerp(elasticity, inelastic, elastic1);
197
200
  }
198
201
  }
199
202
  if (!smallStepUp && (isBlockedX || !isBlockedY)) // resolve x collision
@@ -213,8 +216,8 @@ class EngineObject
213
216
 
214
217
  // lerp betwen elastic or inelastic based on elasticity
215
218
  const elasticity = max(this.elasticity, o.elasticity);
216
- this.velocity.x = lerp(elasticity, elastic0, inelastic);
217
- o.velocity.x = lerp(elasticity, elastic1, inelastic);
219
+ this.velocity.x = lerp(elasticity, inelastic, elastic0);
220
+ o.velocity.x = lerp(elasticity, inelastic, elastic1);
218
221
  }
219
222
  else // bounce if other object is fixed
220
223
  this.velocity.x *= -this.elasticity;
@@ -244,7 +247,7 @@ class EngineObject
244
247
 
245
248
  // adjust next velocity to settle on ground
246
249
  const o = (oldPos.y - this.size.y/2|0) - (oldPos.y - this.size.y/2);
247
- if (o < 0 && o > -1 && o > this.damping * this.velocity.y + gravity * this.gravityScale)
250
+ if (o < 0 && o > this.damping * this.velocity.y + gravity * this.gravityScale)
248
251
  this.velocity.y = this.damping ? (o - gravity * this.gravityScale) / this.damping : 0;
249
252
 
250
253
  // move to previous position
@@ -349,4 +352,20 @@ class EngineObject
349
352
  this.isSolid = isSolid;
350
353
  this.collideTiles = collideTiles;
351
354
  }
355
+
356
+ toString()
357
+ {
358
+ let text = 'type = ' + this.constructor.name;
359
+ if (this.pos.x || this.pos.y)
360
+ text += '\npos = ' + this.pos;
361
+ if (this.velocity.x || this.velocity.y)
362
+ text += '\nvelocity = ' + this.velocity;
363
+ if (this.size.x || this.size.y)
364
+ text += '\nsize = ' + this.size;
365
+ if (this.angle)
366
+ text += '\nangle = ' + this.angle.toFixed(3);
367
+ if (this.color)
368
+ text += '\ncolor = ' + this.color;
369
+ return text;
370
+ }
352
371
  }
@@ -15,9 +15,9 @@
15
15
  * let pos = vec2(2,3);
16
16
  * let particleEmiter = new ParticleEmitter
17
17
  * (
18
- * pos, 1, 0, 500, PI, // pos, emitSize, emitTime, emitRate, emiteCone
18
+ * pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emiteCone
19
19
  * 0, vec2(16), // tileIndex, tileSize
20
- * new Color, new Color(0,0,0), // colorStartA, colorStartB
20
+ * new Color(1,1,1), new Color(0,0,0), // colorStartA, colorStartB
21
21
  * new Color(1,1,1,0), new Color(0,0,0,0), // colorEndA, colorEndB
22
22
  * 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
23
23
  * .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
@@ -28,6 +28,7 @@ class ParticleEmitter extends EngineObject
28
28
  {
29
29
  /** Create a particle system with the given settings
30
30
  * @param {Vector2} position - World space position of the emitter
31
+ * @param {Number} [angle=0] - Angle to emit the particles
31
32
  * @param {Number} [emitSize=0] - World space size of the emitter (float for circle diameter, vec2 for rect)
32
33
  * @param {Number} [emitTime=0] - How long to stay alive (0 is forever)
33
34
  * @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0
@@ -57,6 +58,7 @@ class ParticleEmitter extends EngineObject
57
58
  constructor
58
59
  (
59
60
  pos,
61
+ angle,
60
62
  emitSize = 0,
61
63
  emitTime = 0,
62
64
  emitRate = 100,
@@ -84,7 +86,7 @@ class ParticleEmitter extends EngineObject
84
86
  renderOrder = additive ? 1e9 : 0
85
87
  )
86
88
  {
87
- super(pos, new Vector2, tileIndex, tileSize, 0, undefined, renderOrder);
89
+ super(pos, new Vector2, tileIndex, tileSize, angle, undefined, renderOrder);
88
90
 
89
91
  // emitter settings
90
92
  /** @property {Number} - World space size of the emitter (float for circle diameter, vec2 for rect) */
@@ -145,6 +145,11 @@ let inputWASDEmulateDirection = 1;
145
145
  * @memberof Settings */
146
146
  let touchGamepadEnable = 0;
147
147
 
148
+ /** True if touch gamepad should be analog stick or false to use if 8 way dpad
149
+ * @default
150
+ * @memberof Settings */
151
+ let touchGamepadAnalog = 1;
152
+
148
153
  /** Size of virutal gamepad for touch devices in pixels
149
154
  * @default
150
155
  * @memberof Settings */
@@ -153,14 +153,14 @@ class TileLayerData
153
153
  */
154
154
  class TileLayer extends EngineObject
155
155
  {
156
- /** Create a tile layer object
157
- * @param {Vector2} [position=new Vector2(0,0)] - World space position
158
- * @param {Vector2} [size=objectDefaultSize] - World space size
159
- * @param {Vector2} [tileSize=tileSizeDefault] - Size of tiles in source pixels
160
- * @param {Vector2} [scale=new Vector2(1,1)] - How much to scale this layer when rendered
161
- * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
162
- */
163
- constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1), renderOrder=0)
156
+ /** Create a tile layer object
157
+ * @param {Vector2} [position=new Vector2()] - World space position
158
+ * @param {Vector2} [size=tileCollisionSize] - World space size
159
+ * @param {Vector2} [tileSize=tileSizeDefault] - Size of tiles in source pixels
160
+ * @param {Vector2} [scale=new Vector2(1,1)] - How much to scale this layer when rendered
161
+ * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
162
+ */
163
+ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1), renderOrder=0)
164
164
  {
165
165
  super(pos, size, -1, tileSize, 0, undefined, renderOrder);
166
166
 
@@ -218,17 +218,19 @@ class TileLayer extends EngineObject
218
218
  );
219
219
  }
220
220
 
221
- /** Draw all the tile data to an offscreen canvas */
221
+ /** Draw all the tile data to an offscreen canvas
222
+ * - This may be slow in some browsers
223
+ */
222
224
  redraw()
223
225
  {
224
- this.redrawStart();
226
+ this.redrawStart(1);
225
227
  this.drawAllTileData();
226
228
  this.redrawEnd();
227
229
  }
228
230
 
229
231
  /** Call to start the redraw process
230
- * @param {Boolean} [clear=1] - Should it clear the canvas before drawing */
231
- redrawStart(clear = 1)
232
+ * @param {Boolean} [clear=0] - Should it clear the canvas before drawing */
233
+ redrawStart(clear = 0)
232
234
  {
233
235
  if (clear)
234
236
  {
@@ -318,7 +320,7 @@ class TileLayer extends EngineObject
318
320
  if (tileIndex < 0)
319
321
  {
320
322
  // untextured
321
- context.fillStyle = color.rgba();
323
+ context.fillStyle = color;
322
324
  context.fillRect(-.5, -.5, 1, 1);
323
325
  }
324
326
  else
@@ -337,5 +339,6 @@ class TileLayer extends EngineObject
337
339
  * @param {Vector2} [size=new Vector2(1,1)]
338
340
  * @param {Color} [color=new Color(1,1,1)]
339
341
  * @param {Number} [angle=0] */
340
- drawRect(pos, size, color, angle) { this.drawTile(pos, size, -1, 0, color, angle); }
342
+ drawRect(pos, size, color, angle)
343
+ { this.drawTile(pos, size, -1, 0, color, angle); }
341
344
  }
@@ -49,27 +49,27 @@ const mod = (a, b=1)=> ((a % b) + b) % b;
49
49
 
50
50
  /** Clamps the value beween max and min
51
51
  * @param {Number} value
52
- * @param {Number} [max=1]
53
52
  * @param {Number} [min=0]
53
+ * @param {Number} [max=1]
54
54
  * @return {Number}
55
55
  * @memberof Utilities */
56
- const clamp = (v, max=1, min=0)=> (ASSERT(max > min), v < min ? min : v > max ? max : v);
56
+ const clamp = (v, min=0, max=1)=> v < min ? min : v > max ? max : v;
57
57
 
58
58
  /** Returns what percentage the value is between max and min
59
59
  * @param {Number} value
60
- * @param {Number} [max=1]
61
60
  * @param {Number} [min=0]
61
+ * @param {Number} [max=1]
62
62
  * @return {Number}
63
63
  * @memberof Utilities */
64
- const percent = (v, max=1, min=0)=> max-min ? clamp((v-min) / (max-min)) : 0;
64
+ const percent = (v, min=0, max=1)=> max-min ? clamp((v-min) / (max-min)) : 0;
65
65
 
66
66
  /** Linearly interpolates the percent value between max and min
67
67
  * @param {Number} percent
68
- * @param {Number} [max=1]
69
68
  * @param {Number} [min=0]
69
+ * @param {Number} [max=1]
70
70
  * @return {Number}
71
71
  * @memberof Utilities */
72
- const lerp = (p, max=1, min=0)=> min + clamp(p) * (max-min);
72
+ const lerp = (p, min=0, max=1)=> min + clamp(p) * (max-min);
73
73
 
74
74
  /** Applies smoothstep function to the percentage value
75
75
  * @param {Number} value
@@ -84,11 +84,11 @@ const smoothStep = (p)=> p * p * (3 - 2 * p);
84
84
  const nearestPowerOfTwo = (v)=> 2**Math.ceil(Math.log2(v));
85
85
 
86
86
  /** Returns true if two axis aligned bounding boxes are overlapping
87
- * @param {Vector2} pointA - Center of box A
88
- * @param {Vector2} sizeA - Size of box A
89
- * @param {Vector2} pointB - Center of box B
90
- * @param {Vector2} sizeB - Size of box B
91
- * @return {Boolean} - True if overlapping
87
+ * @param {Vector2} pointA - Center of box A
88
+ * @param {Vector2} sizeA - Size of box A
89
+ * @param {Vector2} pointB - Center of box B
90
+ * @param {Vector2} [sizeB] - Size of box B
91
+ * @return {Boolean} - True if overlapping
92
92
  * @memberof Utilities */
93
93
  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;
94
94
 
@@ -311,7 +311,13 @@ class Vector2
311
311
  /** Returns true if this vector is within the bounds of an array size passed in
312
312
  * @param {Vector2} arraySize
313
313
  * @return {Boolean} */
314
- arrayCheck(arraySize) { return this.x >= 0 && this.y >= 0 && this.x < arraySize.x && this.y < arraySize.y; }
314
+ arrayCheck(arraySize) { return this.x >= 0 && this.y >= 0 && this.x < arraySize.x && this.y < arraySize.y; }
315
+
316
+ /** Returns this vector expressed as a string
317
+ * @param {float} digits - precision to display
318
+ * @return {String} */
319
+ toString(digits=3)
320
+ { return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`; }
315
321
  }
316
322
 
317
323
  ///////////////////////////////////////////////////////////////////////////////
@@ -382,7 +388,7 @@ class Color
382
388
  * @return {Color} */
383
389
  lerp(c, p) { return this.add(c.subtract(this).scale(clamp(p))); }
384
390
 
385
- /** Sets this color given a hue, saturation, lightness , and alpha
391
+ /** Sets this color given a hue, saturation, lightness, and alpha
386
392
  * @param {Number} [hue=0]
387
393
  * @param {Number} [saturation=0]
388
394
  * @param {Number} [lightness=1]
@@ -418,21 +424,43 @@ class Color
418
424
  ).clamp();
419
425
  }
420
426
 
421
- /** Returns this color expressed as an rgba string
427
+ /** Returns this color expressed as an CSS color value
422
428
  * @return {String} */
423
- rgba()
429
+ toString()
424
430
  {
425
431
  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);
426
432
  return `rgb(${this.r*255|0},${this.g*255|0},${this.b*255|0},${this.a})`;
427
433
  }
428
434
 
429
- /** Returns this color expressed as 32 bit integer value
435
+ /** Returns this color expressed as 32 bit integer RGBA value
430
436
  * @return {Number} */
431
437
  rgbaInt()
432
438
  {
433
439
  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);
434
440
  return (this.r*255|0) + (this.g*255<<8) + (this.b*255<<16) + (this.a*255<<24);
435
441
  }
442
+
443
+ /** Returns this color expressed as a hex code
444
+ * @return {String} */
445
+ hex()
446
+ {
447
+ const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
448
+ return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b);
449
+ }
450
+
451
+ /** Set this color from a hex code
452
+ * @param {String} hex - html hex code
453
+ * @return {Color} */
454
+ setHex(hex)
455
+ {
456
+ const fromHex = (a)=> parseInt(hex.slice(a,a+2), 16)/255;
457
+ this.r = fromHex(1);
458
+ this.g = fromHex(3),
459
+ this.b = fromHex(5);
460
+ this.a = 1;
461
+ ASSERT(this.r>=0 && this.r<=1 && this.g>=0 && this.g<=1 && this.b>=0 && this.b<=1);
462
+ return this;
463
+ }
436
464
  }
437
465
 
438
466
  ///////////////////////////////////////////////////////////////////////////////
@@ -469,7 +497,7 @@ class Timer
469
497
 
470
498
  /** Returns true if set and elapsed
471
499
  * @return {Boolean} */
472
- elapsed() { return time > this.time; }
500
+ elapsed() { return time > this.time; }
473
501
 
474
502
  /** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
475
503
  * @return {Number} */
@@ -477,5 +505,9 @@ class Timer
477
505
 
478
506
  /** Get percentage elapsed based on time it was set to, returns 0 if not set
479
507
  * @return {Number} */
480
- getPercent() { return this.isSet()? percent(this.time - time, 0, this.setTime) : 0; }
508
+ getPercent() { return this.isSet()? percent(this.time - time, this.setTime, 0) : 0; }
509
+
510
+ /** Returns this timer expressed as a string
511
+ * @return {String} */
512
+ toString() { return this.unset() ? 'unset' : Math.abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ); }
481
513
  }
@@ -38,8 +38,8 @@ function glInit()
38
38
 
39
39
  // create the canvas and tile texture
40
40
  glCanvas = document.createElement('canvas');
41
- glContext = glCanvas.getContext('webgl');
42
- glCanvas.style = canvaStyle;
41
+ glContext = glCanvas.getContext('webgl', {antialias: false});
42
+ glCanvas.style = styleCanvas;
43
43
  glTileTexture = glCreateTexture(tileImage);
44
44
 
45
45
  // some browsers are much faster without copying the gl buffer so we just overlay it instead
@@ -47,25 +47,24 @@ function glInit()
47
47
 
48
48
  // setup vertex and fragment shaders
49
49
  glShader = glCreateProgram(
50
- 'precision highp float;'+ // use highp for better accuracy, lowp for better perf
51
- 'uniform mat4 m;'+ // transform matrix
52
- 'attribute float a;'+ // angle
53
- 'attribute vec2 p,s,t;'+ // position, size, uv
54
- 'attribute vec4 c,b;'+ // color, additiveColor
55
- 'varying vec2 v;'+ // return uv
56
- 'varying vec4 d,e;'+ // return color, additiveColor
57
- 'void main(){'+ // shader entry point
58
- 'gl_Position=m*vec4((s*cos(a)-vec2(-s.y,s)*sin(a))*.5+p,1,1);'+// transform position
59
- 'v=t;d=c;e=b;'+ // pass stuff to fragment shader
60
- '}' // end of shader
50
+ 'precision highp float;'+ // use highp for better accuracy
51
+ 'uniform mat4 m;'+ // transform matrix
52
+ 'attribute vec2 p,t;'+ // position, uv
53
+ 'attribute vec4 c,a;'+ // color, additiveColor
54
+ 'varying vec2 v;'+ // return uv
55
+ 'varying vec4 d,e;'+ // return color, additiveColor
56
+ 'void main(){'+ // shader entry point
57
+ 'gl_Position=m*vec4(p,1,1);'+ // transform position
58
+ 'v=t;d=c;e=a;'+ // pass stuff to fragment shader
59
+ '}' // end of shader
61
60
  ,
62
- 'precision highp float;'+ // use highp for better accuracy, lowp for better perf
63
- 'varying vec2 v;'+ // uv
64
- 'varying vec4 d,e;'+ // color, additiveColor
65
- 'uniform sampler2D j;'+ // texture
66
- 'void main(){'+ // shader entry point
67
- 'gl_FragColor=texture2D(j,v)*d+e;'+ // modulate texture by color plus additive
68
- '}' // end of shader
61
+ 'precision highp float;'+ // use highp for better accuracy
62
+ 'varying vec2 v;'+ // uv
63
+ 'varying vec4 d,e;'+ // color, additiveColor
64
+ 'uniform sampler2D s;'+ // texture
65
+ 'void main(){'+ // shader entry point
66
+ 'gl_FragColor=texture2D(s,v)*d+e;'+ // modulate texture by color plus additive
67
+ '}' // end of shader
69
68
  );
70
69
 
71
70
  // init buffers
@@ -83,12 +82,10 @@ function glInit()
83
82
  glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
84
83
  offset += size*typeSize;
85
84
  }
86
- initVertexAttribArray('a', gl_FLOAT, 4, 1); // angle
87
85
  initVertexAttribArray('p', gl_FLOAT, 4, 2); // position
88
- initVertexAttribArray('s', gl_FLOAT, 4, 2); // size
89
86
  initVertexAttribArray('t', gl_FLOAT, 4, 2); // texture coords
90
87
  initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
91
- initVertexAttribArray('b', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
88
+ initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
92
89
  }
93
90
 
94
91
  /** Set the WebGl blend mode, normally you should call setBlendMode instead
@@ -203,6 +200,7 @@ function glPreRender(width, height, cameraX, cameraY, cameraScale)
203
200
 
204
201
  // clear and set to same size as main canvas
205
202
  glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height);
203
+ glContext.clear(gl_COLOR_BUFFER_BIT);
206
204
 
207
205
  // set up the shader
208
206
  glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
@@ -249,12 +247,10 @@ function glCopyToContext(context, forceDraw)
249
247
  if (!glEnable || !glBatchCount) return;
250
248
 
251
249
  glFlush();
250
+
251
+ // do not draw in overlay mode because the canvas is visible
252
252
  if (!glOverlay || forceDraw)
253
- {
254
- // do not draw/clear in overlay mode because the canvas is visible
255
253
  context.drawImage(glCanvas, 0, 0);
256
- glContext.clear(gl_COLOR_BUFFER_BIT);
257
- }
258
254
  }
259
255
 
260
256
  /** Add a sprite to the gl draw list, used by all gl draw functions
@@ -277,51 +273,49 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=0xffffff
277
273
  // flush if there is no room for more verts or if different blend mode
278
274
  if (glBatchCount == gl_MAX_BATCH || glBatchAdditive != glAdditive)
279
275
  glFlush();
276
+
277
+ // prepare to create the verts from size and angle
278
+ const c = Math.cos(angle)/2, s = Math.sin(angle)/2;
279
+ const cx = c*sizeX, cy = c*sizeY, sx = s*sizeX, sy = s*sizeY;
280
280
 
281
281
  // setup 2 triangles to form a quad
282
282
  let offset = glBatchCount++ * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT;
283
283
 
284
284
  // vertex 0
285
- glPositionData[offset++] = angle;
286
- glPositionData[offset++] = x; glPositionData[offset++] = y;
287
- glPositionData[offset++] = -sizeX; glPositionData[offset++] = -sizeY;
288
- glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
289
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
285
+ glPositionData[offset++] = x - cx - sy;
286
+ glPositionData[offset++] = y - cy + sx;
287
+ glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
288
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
290
289
 
291
290
  // vertex 1
292
- glPositionData[offset++] = angle;
293
- glPositionData[offset++] = x; glPositionData[offset++] = y;
294
- glPositionData[offset++] = sizeX; glPositionData[offset++] = sizeY;
295
- glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
296
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
291
+ glPositionData[offset++] = x + cx + sy;
292
+ glPositionData[offset++] = y + cy - sx;
293
+ glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
294
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
297
295
 
298
296
  // vertex 2
299
- glPositionData[offset++] = angle;
300
- glPositionData[offset++] = x; glPositionData[offset++] = y;
301
- glPositionData[offset++] = -sizeX; glPositionData[offset++] = sizeY;
302
- glPositionData[offset++] = uv0X; glPositionData[offset++] = uv0Y;
303
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
297
+ glPositionData[offset++] = x - cx + sy;
298
+ glPositionData[offset++] = y + cy + sx;
299
+ glPositionData[offset++] = uv0X; glPositionData[offset++] = uv0Y;
300
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
304
301
 
305
302
  // vertex 0
306
- glPositionData[offset++] = angle;
307
- glPositionData[offset++] = x; glPositionData[offset++] = y;
308
- glPositionData[offset++] = -sizeX; glPositionData[offset++] = -sizeY;
309
- glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
310
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
303
+ glPositionData[offset++] = x - cx - sy;
304
+ glPositionData[offset++] = y - cy + sx;
305
+ glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
306
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
311
307
 
312
308
  // vertex 3
313
- glPositionData[offset++] = angle;
314
- glPositionData[offset++] = x; glPositionData[offset++] = y;
315
- glPositionData[offset++] = sizeX; glPositionData[offset++] = -sizeY;
316
- glPositionData[offset++] = uv1X; glPositionData[offset++] = uv1Y;
317
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
309
+ glPositionData[offset++] = x + cx - sy;
310
+ glPositionData[offset++] = y - cy - sx;
311
+ glPositionData[offset++] = uv1X; glPositionData[offset++] = uv1Y;
312
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
318
313
 
319
314
  // vertex 1
320
- glPositionData[offset++] = angle;
321
- glPositionData[offset++] = x; glPositionData[offset++] = y;
322
- glPositionData[offset++] = sizeX; glPositionData[offset++] = sizeY;
323
- glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
324
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
315
+ glPositionData[offset++] = x + cx + sy;
316
+ glPositionData[offset++] = y + cy - sx;
317
+ glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
318
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
325
319
  }
326
320
 
327
321
  ///////////////////////////////////////////////////////////////////////////////
@@ -353,6 +347,6 @@ gl_LINK_STATUS = 35714,
353
347
 
354
348
  // constants for batch rendering
355
349
  gl_VERTICES_PER_QUAD = 6,
356
- gl_INDICIES_PER_VERT = 9,
350
+ gl_INDICIES_PER_VERT = 6,
357
351
  gl_MAX_BATCH = 1<<16,
358
- gl_VERTEX_BYTE_STRIDE = 4 + (4 * 2) * 3 + (4) * 2; // float + vec2 * 3 + (char * 4) * 2
352
+ gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2; // vec2 * 2 + (char * 4) * 2