littlejsengine 1.2.0 → 1.3.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.
@@ -79,7 +79,7 @@ copy engine.all.release.js %OUTPUT_FILENAME%
79
79
 
80
80
  rem check code with closure
81
81
  move %OUTPUT_FILENAME% %OUTPUT_FILENAME%.temp
82
- call google-closure-compiler --js %OUTPUT_FILENAME%.temp --js_output_file %OUTPUT_FILENAME% --language_out ECMASCRIPT_2019 --warning_level VERBOSE --jscomp_off *
82
+ call npx google-closure-compiler --js=%OUTPUT_FILENAME%.temp --js_output_file=%OUTPUT_FILENAME% --language_out=ECMASCRIPT_2019 --warning_level=VERBOSE --jscomp_off=*
83
83
  del %OUTPUT_FILENAME%.temp
84
84
  if %ERRORLEVEL% NEQ 0 (
85
85
  pause
@@ -87,7 +87,7 @@ if %ERRORLEVEL% NEQ 0 (
87
87
  )
88
88
 
89
89
  rem lightly minify with uglify
90
- call uglifyjs -o %OUTPUT_FILENAME% -- %OUTPUT_FILENAME%
90
+ call npx uglifyjs -o %OUTPUT_FILENAME% -- %OUTPUT_FILENAME%
91
91
  if %ERRORLEVEL% NEQ 0 (
92
92
  pause
93
93
  exit /b %ERRORLEVEL%
@@ -95,7 +95,7 @@ if %ERRORLEVEL% NEQ 0 (
95
95
 
96
96
  rem --- BUILD TYPESCRIPT DEFINITIONS ---
97
97
 
98
- call tsc engine.all.js --declaration --allowJs --emitDeclarationOnly --outFile index.d.ts
98
+ call npx tsc engine.all.js --declaration --allowJs --emitDeclarationOnly --outFile index.d.ts
99
99
  if %ERRORLEVEL% NEQ 0 (
100
100
  pause
101
101
  exit /b %ERRORLEVEL%
@@ -160,7 +160,7 @@ const debugUpdate = ()=>
160
160
  {
161
161
  if (!debug)
162
162
  return;
163
-
163
+
164
164
  if (keyWasPressed(192)) // ~
165
165
  debugOverlay = !debugOverlay;
166
166
  if (debugOverlay)
@@ -273,17 +273,11 @@ const debugRender = ()=>
273
273
  drawRect(mousePos.floor().add(vec2(.5)), vec2(1), new Color(0,0,1,.5));
274
274
  drawLine(mousePos, bestObject.pos, .1, !raycastHitPos ? new Color(0,1,0,.5) : new Color(1,0,0,.5));
275
275
 
276
- let pos = mousePos.copy(), height = vec2(0,.5);
277
- const printVec2 = (v)=> '(' + (v.x>0?' ':'') + (v.x).toFixed(2) + ',' + (v.y>0?' ':'') + (v.y).toFixed(2) + ')';
278
- const args = [.5, new Color, .05, undefined, undefined, 'monospace'];
279
-
280
- drawText('pos = ' + printVec2(bestObject.pos)
281
- + (bestObject.angle>0?' ':' ') + (bestObject.angle*180/PI).toFixed(1) + '°',
282
- pos = pos.add(height), ...args);
283
- drawText('vel = ' + printVec2(bestObject.velocity), pos = pos.add(height), ...args);
284
- drawText('size = ' + printVec2(bestObject.size), pos = pos.add(height), ...args);
285
- drawText('type = ' + ( bestObject.constructor.name), pos = mousePos.subtract(height), ...args);
286
- drawText('collision = ' + getTileCollisionData(mousePos), pos = mousePos.subtract(height.scale(2)), ...args);
276
+ const debugText = 'mouse pos = ' + mousePos +
277
+ '\nmouse collision = ' + getTileCollisionData(mousePos) +
278
+ '\n\n--- object info ---\n' +
279
+ bestObject.toString();
280
+ drawTextScreen(debugText, mousePosScreen, 24, new Color, .05, undefined, undefined, 'monospace');
287
281
  mainContext = saveContext;
288
282
  }
289
283
 
@@ -292,11 +286,12 @@ const debugRender = ()=>
292
286
 
293
287
  {
294
288
  // draw debug primitives
295
- overlayContext.save();
296
289
  overlayContext.lineWidth = 2;
297
290
  const pointSize = debugPointSize * cameraScale;
298
291
  debugPrimitives.forEach(p=>
299
292
  {
293
+ overlayContext.save();
294
+
300
295
  // create canvas transform from world space to screen space
301
296
  const pos = worldToScreen(p.pos);
302
297
 
@@ -332,12 +327,12 @@ const debugRender = ()=>
332
327
  p.fill && overlayContext.fill();
333
328
  overlayContext.stroke();
334
329
  }
330
+
331
+ overlayContext.restore();
335
332
  });
336
333
 
337
334
  // remove expired pritives
338
335
  debugPrimitives = debugPrimitives.filter(r=>r.time.get()<0);
339
-
340
- overlayContext.restore();
341
336
  }
342
337
 
343
338
  {
@@ -110,7 +110,7 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, col
110
110
  if (tileIndex < 0)
111
111
  {
112
112
  // if negative tile index, force untextured
113
- context.fillStyle = color.rgba();
113
+ context.fillStyle = color;
114
114
  context.fillRect(-.5, -.5, 1, 1);
115
115
  }
116
116
  else
@@ -215,7 +215,8 @@ function setBlendMode(additive, useWebGL=glEnable)
215
215
  mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
216
216
  }
217
217
 
218
- /** Draw text on overlay canvas in world space
218
+ /** Draw text on overlay canvas in screen space
219
+ * Automatically splits new lines into rows
219
220
  * @param {String} text
220
221
  * @param {Vector2} pos
221
222
  * @param {Number} [size=1]
@@ -224,20 +225,37 @@ function setBlendMode(additive, useWebGL=glEnable)
224
225
  * @param {Color} [lineColor=new Color(0,0,0)]
225
226
  * @param {String} [textAlign='center']
226
227
  * @memberof Draw */
227
- function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault)
228
+ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault)
228
229
  {
229
- pos = worldToScreen(pos);
230
- overlayContext.font = size*cameraScale + 'px '+ font;
230
+ overlayContext.fillStyle = color;
231
+ overlayContext.lineWidth = lineWidth *= cameraScale;
232
+ overlayContext.strokeStyle = lineColor;
231
233
  overlayContext.textAlign = textAlign;
234
+ overlayContext.font = size + 'px '+ font;
232
235
  overlayContext.textBaseline = 'middle';
233
- if (lineWidth)
236
+
237
+ pos = pos.copy();
238
+ text.split('\n').forEach(line=>
234
239
  {
235
- overlayContext.lineWidth = lineWidth*cameraScale;
236
- overlayContext.strokeStyle = lineColor.rgba();
237
- overlayContext.strokeText(text, pos.x, pos.y);
238
- }
239
- overlayContext.fillStyle = color.rgba();
240
- overlayContext.fillText(text, pos.x, pos.y);
240
+ lineWidth && overlayContext.strokeText(line, pos.x, pos.y);
241
+ overlayContext.fillText(line, pos.x, pos.y);
242
+ pos.y += size;
243
+ });
244
+ }
245
+
246
+ /** Draw text on overlay canvas in world space
247
+ * Automatically splits new lines into rows
248
+ * @param {String} text
249
+ * @param {Vector2} pos
250
+ * @param {Number} [size=1]
251
+ * @param {Color} [color=new Color(1,1,1)]
252
+ * @param {Number} [lineWidth=0]
253
+ * @param {Color} [lineColor=new Color(0,0,0)]
254
+ * @param {String} [textAlign='center']
255
+ * @memberof Draw */
256
+ function drawText(text, pos, size=1, color, lineWidth, lineColor, textAlign, font)
257
+ {
258
+ drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth, lineColor, textAlign, font);
241
259
  }
242
260
 
243
261
  ///////////////////////////////////////////////////////////////////////////////
@@ -254,6 +272,9 @@ function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new
254
272
  * // draw text
255
273
  * font.drawTextScreen("LittleJS\nHello World!", vec2(200, 50));
256
274
  */
275
+
276
+ let engineFontImage;
277
+
257
278
  class FontImage
258
279
  {
259
280
  /** Create an image font
@@ -265,14 +286,14 @@ class FontImage
265
286
  */
266
287
  constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), startTileIndex=0, context=overlayContext)
267
288
  {
268
- if (!image)
289
+ if (!image && !engineFontImage)
269
290
  {
270
- // use default font image
271
- image = new Image();
272
- 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';
291
+ // load default font image
292
+ engineFontImage = new Image();
293
+ engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
273
294
  }
274
295
 
275
- this.image = image;
296
+ this.image = image || engineFontImage;
276
297
  this.tileSize = tileSize;
277
298
  this.paddingSize = paddingSize;
278
299
  this.startTileIndex = startTileIndex;
Binary file
@@ -281,7 +281,7 @@ if (isTouchDevice)
281
281
  // touch gamepad, virtual on screen gamepad emulator for touch devices
282
282
 
283
283
  // touch input internal variables
284
- let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick = vec2(), touchGamepadAnalog = 1;
284
+ let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick = vec2();
285
285
 
286
286
  // create the touch gamepad, called automatically by the engine
287
287
  function touchGamepadCreate()
@@ -328,7 +328,7 @@ function touchGamepadCreate()
328
328
  {
329
329
  // virtual analog stick
330
330
  if (touchGamepadAnalog)
331
- touchGamepadStick = touchPos.subtract(stickCenter).clampLength(touchGamepadSize/2).scale(2/touchGamepadSize);
331
+ touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
332
332
  else
333
333
  {
334
334
  // 8 way dpad
@@ -369,12 +369,28 @@ function touchGamepadRender()
369
369
  overlayContext.lineWidth = 3;
370
370
 
371
371
  // draw left analog stick
372
- overlayContext.fillStyle = gamepadStick(0).length() > 0 ? '#fff' : '#000';
372
+ overlayContext.fillStyle = touchGamepadStick.lengthSquared() > 0 ? '#fff' : '#000';
373
373
  overlayContext.beginPath();
374
- overlayContext.arc(touchGamepadSize, mainCanvasSize.y-touchGamepadSize, touchGamepadSize/2, 0,9);
375
- overlayContext.fill();
376
- overlayContext.stroke();
377
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
+
378
394
  // draw right face buttons
379
395
  const rightCenter = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
380
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
@@ -36,7 +36,7 @@ class EngineObject
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
  */
@@ -352,4 +352,20 @@ class EngineObject
352
352
  this.isSolid = isSolid;
353
353
  this.collideTiles = collideTiles;
354
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
+ }
355
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()] - 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
 
@@ -320,7 +320,7 @@ class TileLayer extends EngineObject
320
320
  if (tileIndex < 0)
321
321
  {
322
322
  // untextured
323
- context.fillStyle = color.rgba();
323
+ context.fillStyle = color;
324
324
  context.fillRect(-.5, -.5, 1, 1);
325
325
  }
326
326
  else
@@ -339,5 +339,6 @@ class TileLayer extends EngineObject
339
339
  * @param {Vector2} [size=new Vector2(1,1)]
340
340
  * @param {Color} [color=new Color(1,1,1)]
341
341
  * @param {Number} [angle=0] */
342
- 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); }
343
344
  }
@@ -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} */
@@ -478,4 +506,8 @@ class Timer
478
506
  /** Get percentage elapsed based on time it was set to, returns 0 if not set
479
507
  * @return {Number} */
480
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
  }
@@ -47,24 +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 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
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
60
60
  ,
61
- 'precision highp float;'+ // use highp for better accuracy, lowp for better perf
62
- 'varying vec2 v;'+ // uv
63
- 'varying vec4 d,e;'+ // color, additiveColor
64
- 'uniform sampler2D j;'+ // texture
65
- 'void main(){'+ // shader entry point
66
- 'gl_FragColor=texture2D(j,v)*d+e;'+ // modulate texture by color plus additive
67
- '}' // 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
68
68
  );
69
69
 
70
70
  // init buffers
@@ -276,44 +276,44 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=0xffffff
276
276
 
277
277
  // prepare to create the verts from size and angle
278
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;
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++] = x - cX - sY;
286
- glPositionData[offset++] = y - cY + sX;
285
+ glPositionData[offset++] = x - cx - sy;
286
+ glPositionData[offset++] = y - cy + sx;
287
287
  glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
288
288
  glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
289
289
 
290
290
  // vertex 1
291
- glPositionData[offset++] = x + cX + sY;
292
- glPositionData[offset++] = y + cY - sX;
291
+ glPositionData[offset++] = x + cx + sy;
292
+ glPositionData[offset++] = y + cy - sx;
293
293
  glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
294
294
  glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
295
295
 
296
296
  // vertex 2
297
- glPositionData[offset++] = x - cX + sY;
298
- glPositionData[offset++] = y + cY + sX;
297
+ glPositionData[offset++] = x - cx + sy;
298
+ glPositionData[offset++] = y + cy + sx;
299
299
  glPositionData[offset++] = uv0X; glPositionData[offset++] = uv0Y;
300
300
  glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
301
301
 
302
302
  // vertex 0
303
- glPositionData[offset++] = x - cX - sY;
304
- glPositionData[offset++] = y - cY + sX;
303
+ glPositionData[offset++] = x - cx - sy;
304
+ glPositionData[offset++] = y - cy + sx;
305
305
  glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
306
306
  glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
307
307
 
308
308
  // vertex 3
309
- glPositionData[offset++] = x + cX - sY;
310
- glPositionData[offset++] = y - cY - sX;
309
+ glPositionData[offset++] = x + cx - sy;
310
+ glPositionData[offset++] = y - cy - sx;
311
311
  glPositionData[offset++] = uv1X; glPositionData[offset++] = uv1Y;
312
312
  glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
313
313
 
314
314
  // vertex 1
315
- glPositionData[offset++] = x + cX + sY;
316
- glPositionData[offset++] = y + cY - sX;
315
+ glPositionData[offset++] = x + cx + sy;
316
+ glPositionData[offset++] = y + cy - sx;
317
317
  glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
318
318
  glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
319
319
  }