littlejsengine 1.9.5 → 1.9.6

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.
@@ -122,7 +122,7 @@ function gamepadWasReleased(button, gamepad=0)
122
122
  * @return {Vector2}
123
123
  * @memberof Input */
124
124
  function gamepadStick(stick, gamepad=0)
125
- { return stickData[gamepad] ? stickData[gamepad][stick] || vec2() : vec2(); }
125
+ { return gamepadStickData[gamepad] ? gamepadStickData[gamepad][stick] || vec2() : vec2(); }
126
126
 
127
127
  ///////////////////////////////////////////////////////////////////////////////
128
128
  // Input update called by engine
@@ -133,6 +133,8 @@ let inputData = [[]];
133
133
 
134
134
  function inputUpdate()
135
135
  {
136
+ if (headlessMode) return;
137
+
136
138
  // clear input when lost focus (prevent stuck keys)
137
139
  isTouchDevice || document.hasFocus() || clearInput();
138
140
 
@@ -145,6 +147,8 @@ function inputUpdate()
145
147
 
146
148
  function inputUpdatePost()
147
149
  {
150
+ if (headlessMode) return;
151
+
148
152
  // clear input to prepare for next frame
149
153
  for (const deviceInputData of inputData)
150
154
  for (const i in deviceInputData)
@@ -153,9 +157,12 @@ function inputUpdatePost()
153
157
  }
154
158
 
155
159
  ///////////////////////////////////////////////////////////////////////////////
156
- // Keyboard event handlers
160
+ // Input event handlers
157
161
 
162
+ function inputInit()
158
163
  {
164
+ if (headlessMode) return;
165
+
159
166
  onkeydown = (e)=>
160
167
  {
161
168
  if (debug && e.target != document.body) return;
@@ -186,21 +193,29 @@ function inputUpdatePost()
186
193
  c == 'KeyA' ? 'ArrowLeft' :
187
194
  c == 'KeyD' ? 'ArrowRight' : c : c;
188
195
  }
196
+
197
+ // mouse event handlers
198
+ onmousedown = (e)=>
199
+ {
200
+ isUsingGamepad = false;
201
+ inputData[0][e.button] = 3;
202
+ mousePosScreen = mouseToScreen(e);
203
+ e.button && e.preventDefault();
204
+ }
205
+ onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
206
+ onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
207
+ onwheel = (e)=> mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
208
+ oncontextmenu = (e)=> false; // prevent right click menu
209
+
210
+ // init touch input
211
+ if (isTouchDevice)
212
+ touchInputInit();
189
213
  }
190
214
 
191
- ///////////////////////////////////////////////////////////////////////////////
192
- // Mouse event handlers
193
-
194
- onmousedown = (e)=> {isUsingGamepad = false; inputData[0][e.button] = 3; mousePosScreen = mouseToScreen(e); e.button && e.preventDefault();}
195
- onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
196
- onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
197
- onwheel = (e)=> mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
198
- oncontextmenu = (e)=> false; // prevent right click menu
199
-
200
215
  // convert a mouse or touch event position to screen space
201
216
  function mouseToScreen(mousePos)
202
217
  {
203
- if (!mainCanvas)
218
+ if (!mainCanvas || headlessMode)
204
219
  return vec2(); // fix bug that can occur if user clicks before page loads
205
220
 
206
221
  const rect = mainCanvas.getBoundingClientRect();
@@ -212,7 +227,7 @@ function mouseToScreen(mousePos)
212
227
  // Gamepad input
213
228
 
214
229
  // gamepad internal variables
215
- const stickData = [];
230
+ const gamepadStickData = [];
216
231
 
217
232
  // gamepads are updated by engine every frame automatically
218
233
  function gamepadsUpdate()
@@ -229,14 +244,11 @@ function gamepadsUpdate()
229
244
  // update touch gamepad if enabled
230
245
  if (touchGamepadEnable && isTouchDevice)
231
246
  {
232
- // create the touch gamepad if it doesn't exist
233
- if (!touchGamepadButtons)
234
- createTouchGamepad();
235
-
247
+ ASSERT(touchGamepadButtons, 'set touchGamepadEnable before calling init!');
236
248
  if (touchGamepadTimer.isSet())
237
249
  {
238
250
  // read virtual analog stick
239
- const sticks = stickData[0] || (stickData[0] = []);
251
+ const sticks = gamepadStickData[0] || (gamepadStickData[0] = []);
240
252
  sticks[0] = vec2();
241
253
  if (touchGamepadAnalog)
242
254
  sticks[0] = applyDeadZones(touchGamepadStick);
@@ -253,7 +265,8 @@ function gamepadsUpdate()
253
265
  for (let i=10; i--;)
254
266
  {
255
267
  const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
256
- data[j] = touchGamepadButtons[i] ? gamepadIsDown(j,0) ? 1 : 3 : gamepadIsDown(j,0) ? 4 : 0;
268
+ const wasDown = gamepadIsDown(j,0);
269
+ data[j] = touchGamepadButtons[i] ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
257
270
  }
258
271
  }
259
272
  }
@@ -273,7 +286,7 @@ function gamepadsUpdate()
273
286
  // get or create gamepad data
274
287
  const gamepad = gamepads[i];
275
288
  const data = inputData[i+1] || (inputData[i+1] = []);
276
- const sticks = stickData[i] || (stickData[i] = []);
289
+ const sticks = gamepadStickData[i] || (gamepadStickData[i] = []);
277
290
 
278
291
  if (gamepad)
279
292
  {
@@ -312,28 +325,44 @@ function gamepadsUpdate()
312
325
  * @param {Number|Array} [pattern] - single value in ms or vibration interval array
313
326
  * @memberof Input */
314
327
  function vibrate(pattern=100)
315
- { vibrateEnable && navigator && navigator.vibrate && navigator.vibrate(pattern); }
328
+ { vibrateEnable && !headlessMode && navigator && navigator.vibrate && navigator.vibrate(pattern); }
316
329
 
317
330
  /** Cancel any ongoing vibration
318
331
  * @memberof Input */
319
332
  function vibrateStop() { vibrate(0); }
320
333
 
321
334
  ///////////////////////////////////////////////////////////////////////////////
322
- // Touch input
335
+ // Touch input & virtual on screen gamepad
323
336
 
324
337
  /** True if a touch device has been detected
325
338
  * @memberof Input */
326
- const isTouchDevice = window.ontouchstart !== undefined;
339
+ const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
340
+
341
+ // touch gamepad internal variables
342
+ let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
327
343
 
328
344
  // try to enable touch mouse
329
- if (isTouchDevice)
345
+ function touchInputInit()
330
346
  {
347
+ // add non passive touch event listeners
348
+ let handleTouch = handleTouchDefault;
349
+ if (touchGamepadEnable)
350
+ {
351
+ // touch input internal variables
352
+ handleTouch = handleTouchGamepad;
353
+ touchGamepadButtons = [];
354
+ touchGamepadStick = vec2();
355
+ }
356
+ document.addEventListener('touchstart', (e) => handleTouch(e), { passive: false });
357
+ document.addEventListener('touchmove', (e) => handleTouch(e), { passive: false });
358
+ document.addEventListener('touchend', (e) => handleTouch(e), { passive: false });
359
+
331
360
  // override mouse events
332
- let wasTouching;
333
361
  onmousedown = onmouseup = ()=> 0;
334
362
 
335
363
  // handle all touch events the same way
336
- ontouchstart = ontouchmove = ontouchend = (e)=>
364
+ let wasTouching;
365
+ function handleTouchDefault(e)
337
366
  {
338
367
  // fix stalled audio requiring user interaction
339
368
  if (soundEnable && audioContext && audioContext.state != 'running')
@@ -362,27 +391,14 @@ if (isTouchDevice)
362
391
  // must return true so the document will get focus
363
392
  return true;
364
393
  }
365
- }
366
-
367
- ///////////////////////////////////////////////////////////////////////////////
368
- // touch gamepad, virtual on screen gamepad emulator for touch devices
369
-
370
- // touch input internal variables
371
- let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
372
394
 
373
- // create the touch gamepad, called automatically by the engine
374
- function createTouchGamepad()
375
- {
376
- // touch input internal variables
377
- touchGamepadButtons = [];
378
- touchGamepadStick = vec2();
379
-
380
- const touchHandler = ontouchstart;
381
- ontouchstart = ontouchmove = ontouchend = (e)=>
395
+ // special handling for virtual gamepad mode
396
+ function handleTouchGamepad(e)
382
397
  {
383
398
  // clear touch gamepad input
384
399
  touchGamepadStick = vec2();
385
400
  touchGamepadButtons = [];
401
+ isUsingGamepad = true;
386
402
 
387
403
  const touching = e.touches.length;
388
404
  if (touching)
@@ -423,9 +439,8 @@ function createTouchGamepad()
423
439
  }
424
440
  }
425
441
 
426
- // call default touch handler and set to using gamepad
427
- touchHandler.bind(window)(e);
428
- isUsingGamepad = true;
442
+ // call default touch handler so normal touch events still work
443
+ handleTouchDefault(e);
429
444
 
430
445
  // must return true so the document will get focus
431
446
  return true;
@@ -444,32 +459,33 @@ function touchGamepadRender()
444
459
  return;
445
460
 
446
461
  // setup the canvas
447
- overlayContext.save();
448
- overlayContext.globalAlpha = alpha*touchGamepadAlpha;
449
- overlayContext.strokeStyle = '#fff';
450
- overlayContext.lineWidth = 3;
462
+ const context = overlayContext;
463
+ context.save();
464
+ context.globalAlpha = alpha*touchGamepadAlpha;
465
+ context.strokeStyle = '#fff';
466
+ context.lineWidth = 3;
451
467
 
452
468
  // draw left analog stick
453
- overlayContext.fillStyle = touchGamepadStick.lengthSquared() > 0 ? '#fff' : '#000';
454
- overlayContext.beginPath();
469
+ context.fillStyle = touchGamepadStick.lengthSquared() > 0 ? '#fff' : '#000';
470
+ context.beginPath();
455
471
 
456
472
  const leftCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
457
473
  if (touchGamepadAnalog) // draw circle shaped gamepad
458
474
  {
459
- overlayContext.arc(leftCenter.x, leftCenter.y, touchGamepadSize/2, 0, 9);
460
- overlayContext.fill();
461
- overlayContext.stroke();
475
+ context.arc(leftCenter.x, leftCenter.y, touchGamepadSize/2, 0, 9);
476
+ context.fill();
477
+ context.stroke();
462
478
  }
463
479
  else // draw cross shaped gamepad
464
480
  {
465
481
  for(let i=10; i--;)
466
482
  {
467
483
  const angle = i*PI/4;
468
- overlayContext.arc(leftCenter.x, leftCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
469
- i%2 && overlayContext.arc(leftCenter.x, leftCenter.y, touchGamepadSize*.33, angle, angle);
470
- i==1 && overlayContext.fill();
484
+ context.arc(leftCenter.x, leftCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
485
+ i%2 && context.arc(leftCenter.x, leftCenter.y, touchGamepadSize*.33, angle, angle);
486
+ i==1 && context.fill();
471
487
  }
472
- overlayContext.stroke();
488
+ context.stroke();
473
489
  }
474
490
 
475
491
  // draw right face buttons
@@ -477,13 +493,13 @@ function touchGamepadRender()
477
493
  for (let i=4; i--;)
478
494
  {
479
495
  const pos = rightCenter.add(vec2().setDirection(i, touchGamepadSize/2));
480
- overlayContext.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
481
- overlayContext.beginPath();
482
- overlayContext.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
483
- overlayContext.fill();
484
- overlayContext.stroke();
496
+ context.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
497
+ context.beginPath();
498
+ context.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
499
+ context.fill();
500
+ context.stroke();
485
501
  }
486
502
 
487
503
  // set canvas back to normal
488
- overlayContext.restore();
504
+ context.restore();
489
505
  }
@@ -108,18 +108,30 @@ class EngineObject
108
108
  engineObjects.push(this);
109
109
  }
110
110
 
111
- /** Update the object transform and physics, called automatically by engine once each frame */
112
- update()
111
+ /** Update the object transform, called automatically by engine even when paused */
112
+ updateTransforms()
113
113
  {
114
114
  const parent = this.parent;
115
115
  if (parent)
116
116
  {
117
117
  // copy parent pos/angle
118
- this.pos = this.localPos.multiply(vec2(parent.getMirrorSign(),1)).rotate(-parent.angle).add(parent.pos);
119
- this.angle = parent.getMirrorSign()*this.localAngle + parent.angle;
120
- return;
118
+ const mirror = parent.getMirrorSign();
119
+ this.pos = this.localPos.multiply(vec2(mirror,1)).rotate(-parent.angle).add(parent.pos);
120
+ this.angle = mirror*this.localAngle + parent.angle;
121
121
  }
122
122
 
123
+ // update children
124
+ for (const child of this.children)
125
+ child.updateTransforms();
126
+ }
127
+
128
+ /** Update the object physics, called automatically by engine once each frame */
129
+ update()
130
+ {
131
+ // child objects do not have physics
132
+ if (this.parent)
133
+ return;
134
+
123
135
  // limit max speed to prevent missing collisions
124
136
  this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
125
137
  this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
@@ -134,8 +146,7 @@ class EngineObject
134
146
  // physics sanity checks
135
147
  ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
136
148
  ASSERT(this.damping >= 0 && this.damping <= 1);
137
-
138
- if (!enablePhysicsSolver || !this.mass) // do not update collision for fixed objects
149
+ if (!enablePhysicsSolver || !this.mass) // dont do collision for fixed objects
139
150
  return;
140
151
 
141
152
  const wasMovingDown = this.velocity.y < 0;
@@ -233,20 +233,21 @@ class ParticleEmitter extends EngineObject
233
233
  class Particle extends EngineObject
234
234
  {
235
235
  /**
236
- * Create a particle with the given shis.colorStart = undefined;ettings
237
- * @param {Vector2} position - World space position of the particle
238
- * @param {TileInfo} [tileInfo] - Tile info to render particles
239
- * @param {Number} [angle] - Angle to rotate the particle
240
- * @param {Color} [colorStart] - Color at start of life
241
- * @param {Color} [colorEnd] - Color at end of life
242
- * @param {Number} [lifeTime] - How long to live for
243
- * @param {Number} [sizeStart] - Angle to rotate the particle
244
- * @param {Number} [sizeEnd] - Angle to rotate the particle
245
- * @param {Number} [fadeRate] - Angle to rotate the particle
246
- * @param {Boolean} [additive] - Angle to rotate the particle
247
- * @param {Number} [trailScale] - If a trail, how long to make it
236
+ * Create a particle with the passed in settings
237
+ * Typically this is created automatically by a ParticleEmitter
238
+ * @param {Vector2} position - World space position of the particle
239
+ * @param {TileInfo} tileInfo - Tile info to render particles
240
+ * @param {Number} angle - Angle to rotate the particle
241
+ * @param {Color} colorStart - Color at start of life
242
+ * @param {Color} colorEnd - Color at end of life
243
+ * @param {Number} lifeTime - How long to live for
244
+ * @param {Number} sizeStart - Size at start of life
245
+ * @param {Number} sizeEnd - Size at end of life
246
+ * @param {Number} fadeRate - How quick to fade in/out
247
+ * @param {Boolean} additive - Does it use additive blend mode
248
+ * @param {Number} trailScale - If a trail, how long to make it
248
249
  * @param {ParticleEmitter} [localSpaceEmitter] - Parent emitter if local space
249
- * @param {Function} [destroyCallback] - Called when particle dies
250
+ * @param {Function} [destroyCallback] - Callback when particle dies
250
251
  */
251
252
  constructor(position, tileInfo, angle, colorStart, colorEnd, lifeTime, sizeStart, sizeEnd, fadeRate, additive, trailScale, localSpaceEmitter, destroyCallback
252
253
  )
@@ -55,6 +55,12 @@ let fontDefault = 'arial';
55
55
  * @memberof Settings */
56
56
  let showSplashScreen = false;
57
57
 
58
+ /** Disables all rendering, audio, and input for servers
59
+ * @type {Boolean}
60
+ * @default
61
+ * @memberof Settings */
62
+ let headlessMode = false;
63
+
58
64
  ///////////////////////////////////////////////////////////////////////////////
59
65
  // WebGL settings
60
66
 
@@ -83,7 +89,7 @@ let tileSizeDefault = vec2(16);
83
89
  * @type {Number}
84
90
  * @default
85
91
  * @memberof Settings */
86
- let tileFixBleedScale = .1;
92
+ let tileFixBleedScale = .5;
87
93
 
88
94
  ///////////////////////////////////////////////////////////////////////////////
89
95
  // Object settings
@@ -208,7 +214,7 @@ let soundEnable = true;
208
214
  * @type {Number}
209
215
  * @default
210
216
  * @memberof Settings */
211
- let soundVolume = .5;
217
+ let soundVolume = .3;
212
218
 
213
219
  /** Default range where sound no longer plays
214
220
  * @type {Number}
@@ -293,6 +299,11 @@ function setFontDefault(font) { fontDefault = font; }
293
299
  * @memberof Settings */
294
300
  function setShowSplashScreen(show) { showSplashScreen = show; }
295
301
 
302
+ /** Set to disalbe rendering, audio, and input for servers
303
+ * @param {Boolean} headless
304
+ * @memberof Settings */
305
+ function setHeadlessMode(headless) { headlessMode = headless; }
306
+
296
307
  /** Set if webgl rendering is enabled
297
308
  * @param {Boolean} enable
298
309
  * @memberof Settings */
@@ -181,7 +181,7 @@ class TileLayer extends EngineObject
181
181
 
182
182
  /** @property {HTMLCanvasElement} - The canvas used by this tile layer */
183
183
  this.canvas = document.createElement('canvas');
184
- /** @property {CanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
184
+ /** @property {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
185
185
  this.context = this.canvas.getContext('2d');
186
186
  /** @property {Vector2} - How much to scale this layer when rendered */
187
187
  this.scale = scale;
@@ -192,6 +192,17 @@ class TileLayer extends EngineObject
192
192
  this.data = [];
193
193
  for (let j = this.size.area(); j--;)
194
194
  this.data.push(new TileLayerData);
195
+
196
+ if (headlessMode)
197
+ {
198
+ // disable rendering
199
+ this.redraw = () => {};
200
+ this.render = () => {};
201
+ this.redrawStart = () => {};
202
+ this.redrawEnd = () => {};
203
+ this.drawTileData = () => {};
204
+ this.drawCanvas2D = () => {};
205
+ }
195
206
  }
196
207
 
197
208
  /** Set data at a given position in the array
@@ -222,7 +233,7 @@ class TileLayer extends EngineObject
222
233
  ASSERT(mainContext != this.context, 'must call redrawEnd() after drawing tiles');
223
234
 
224
235
  // flush and copy gl canvas because tile canvas does not use webgl
225
- glEnable && !glOverlay && !this.isOverlay && glCopyToContext(mainContext);
236
+ !glOverlay && !this.isOverlay && glCopyToContext(mainContext);
226
237
 
227
238
  // draw the entire cached level onto the canvas
228
239
  const pos = worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));
@@ -272,14 +283,14 @@ class TileLayer extends EngineObject
272
283
  this.context.imageSmoothingEnabled = !canvasPixelated;
273
284
 
274
285
  // setup gl rendering if enabled
275
- glEnable && glPreRender();
286
+ glPreRender();
276
287
  }
277
288
 
278
289
  /** Call to end the redraw process */
279
290
  redrawEnd()
280
291
  {
281
292
  ASSERT(mainContext == this.context, 'must call redrawStart() before drawing tiles');
282
- glEnable && glCopyToContext(mainContext, true);
293
+ glCopyToContext(mainContext, true);
283
294
  //debugSaveCanvas(this.canvas);
284
295
 
285
296
  // set stuff back to normal
@@ -64,7 +64,7 @@ function clamp(value, min=0, max=1) { return value < min ? min : value > max ? m
64
64
  * @return {Number}
65
65
  * @memberof Utilities */
66
66
  function percent(value, valueA, valueB)
67
- { return valueB-valueA ? clamp((value-valueA) / (valueB-valueA)) : 0; }
67
+ { return (valueB-=valueA) ? clamp((value-valueA)/valueB) : 0; }
68
68
 
69
69
  /** Linearly interpolates between values passed in using percent
70
70
  * @param {Number} percent
@@ -271,7 +271,7 @@ class RandomGenerator
271
271
  this.seed ^= this.seed << 13;
272
272
  this.seed ^= this.seed >>> 17;
273
273
  this.seed ^= this.seed << 5;
274
- return valueB + (valueA - valueB) * abs(this.seed % 1e9) / 1e9;
274
+ return valueB + (valueA - valueB) * abs(this.seed % 1e8) / 1e8;
275
275
  }
276
276
 
277
277
  /** Returns a floored seeded random value the two values passed in
@@ -282,7 +282,7 @@ class RandomGenerator
282
282
 
283
283
  /** Randomly returns either -1 or 1 deterministically
284
284
  * @return {Number} */
285
- sign() { return this.int(2) * 2 - 1; }
285
+ sign() { return this.float() > .5 ? 1 : -1; }
286
286
  }
287
287
 
288
288
  ///////////////////////////////////////////////////////////////////////////////
@@ -330,6 +330,7 @@ class Vector2
330
330
  * @param {Number} [y] - Y axis location */
331
331
  constructor(x=0, y=0)
332
332
  {
333
+ ASSERT(typeof x === 'number' && typeof y === 'number');
333
334
  /** @property {Number} - X axis location */
334
335
  this.x = x;
335
336
  /** @property {Number} - Y axis location */
@@ -656,12 +657,14 @@ class Color
656
657
  * @return {Color} */
657
658
  setHSLA(h=0, s=0, l=1, a=1)
658
659
  {
660
+ h = mod(h,1);
661
+ s = clamp(s);
662
+ l = clamp(l);
659
663
  const q = l < .5 ? l*(1+s) : l+s-l*s, p = 2*l-q,
660
664
  f = (p, q, t)=>
661
- (t = ((t%1)+1)%1) < 1/6 ? p+(q-p)*6*t :
662
- t < 1/2 ? q :
663
- t < 2/3 ? p+(q-p)*(2/3-t)*6 : p;
664
-
665
+ (t = mod(t,1))*6 < 1 ? p+(q-p)*6*t :
666
+ t*2 < 1 ? q :
667
+ t*3 < 2 ? p+(q-p)*(4-t*6) : p;
665
668
  this.r = f(p, q, h + 1/3);
666
669
  this.g = f(p, q, h);
667
670
  this.b = f(p, q, h - 1/3);
@@ -712,13 +715,12 @@ class Color
712
715
  ).clamp();
713
716
  }
714
717
 
715
- /** Returns this color expressed as a hex color code
718
+ /** Returns this color expressed as a rgb color code
716
719
  * @param {Boolean} [useAlpha] - if alpha should be included in result
717
720
  * @return {String} */
718
- toString(useAlpha = true)
719
- {
720
- const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
721
- return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
721
+ toString(useAlpha = true)
722
+ {
723
+ return `rgb(${this.r*255},${this.g*255},${this.b*255},${useAlpha ? this.a : 0})`;
722
724
  }
723
725
 
724
726
  /** Set this color from a hex code
@@ -776,11 +778,11 @@ class Timer
776
778
 
777
779
  /** Returns true if set and has not elapsed
778
780
  * @return {Boolean} */
779
- active() { return time <= this.time; }
781
+ active() { return time < this.time; }
780
782
 
781
783
  /** Returns true if set and elapsed
782
784
  * @return {Boolean} */
783
- elapsed() { return time > this.time; }
785
+ elapsed() { return time >= this.time; }
784
786
 
785
787
  /** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
786
788
  * @return {Number} */
@@ -30,6 +30,8 @@ let glShader, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData,
30
30
  // Initalize WebGL, called automatically by the engine
31
31
  function glInit()
32
32
  {
33
+ if (!glEnable || headlessMode) return;
34
+
33
35
  // create the canvas and textures
34
36
  glCanvas = document.createElement('canvas');
35
37
  glContext = glCanvas.getContext('webgl2');
@@ -42,11 +44,11 @@ function glInit()
42
44
  '#version 300 es\n' + // specify GLSL ES version
43
45
  'precision highp float;'+ // use highp for better accuracy
44
46
  'uniform mat4 m;'+ // transform matrix
45
- 'in vec2 g;'+ // geometry
46
- 'in vec4 p,u,c,a;'+ // position/size, uvs, color, additiveColor
47
- 'in float r;'+ // rotation
48
- 'out vec2 v;'+ // return uv, color, additiveColor
49
- 'out vec4 d,e;'+ // return uv, color, additiveColor
47
+ 'in vec2 g;'+ // in: geometry
48
+ 'in vec4 p,u,c,a;'+ // in: position/size, uvs, color, additiveColor
49
+ 'in float r;'+ // in: rotation
50
+ 'out vec2 v;'+ // out: uv
51
+ 'out vec4 d,e;'+ // out: color, additiveColor
50
52
  'void main(){'+ // shader entry point
51
53
  'vec2 s=(g-.5)*p.zw;'+ // get size offset
52
54
  'gl_Position=m*vec4(p.xy+s*cos(r)-vec2(-s.y,s)*sin(r),1,1);'+ // transform position
@@ -56,10 +58,10 @@ function glInit()
56
58
  ,
57
59
  '#version 300 es\n' + // specify GLSL ES version
58
60
  'precision highp float;'+ // use highp for better accuracy
59
- 'in vec2 v;'+ // uv
60
- 'in vec4 d,e;'+ // color, additiveColor
61
61
  'uniform sampler2D s;'+ // texture
62
- 'out vec4 c;'+ // out color
62
+ 'in vec2 v;'+ // in: uv
63
+ 'in vec4 d,e;'+ // in: color, additiveColor
64
+ 'out vec4 c;'+ // out: color
63
65
  'void main(){'+ // shader entry point
64
66
  'c=texture(s,v)*d+e;'+ // modulate texture by color plus additive
65
67
  '}' // end of shader
@@ -81,9 +83,11 @@ function glInit()
81
83
  // Setup render each frame, called automatically by engine
82
84
  function glPreRender()
83
85
  {
86
+ if (!glEnable || headlessMode) return;
87
+
84
88
  // clear and set to same size as main canvas
85
89
  glContext.viewport(0, 0, glCanvas.width=mainCanvas.width, glCanvas.height=mainCanvas.height);
86
- glContext.clear(gl_COLOR_BUFFER_BIT);
90
+ //glContext.clear(gl_COLOR_BUFFER_BIT); // auto cleared when size is set
87
91
 
88
92
  // set up the shader
89
93
  glContext.useProgram(glShader);
@@ -117,12 +121,12 @@ function glPreRender()
117
121
  const s = vec2(2*cameraScale).divide(mainCanvasSize);
118
122
  const p = vec2(-1).subtract(cameraPos.multiply(s));
119
123
  glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), false,
120
- new Float32Array([
124
+ [
121
125
  s.x, 0, 0, 0,
122
126
  0, s.y, 0, 0,
123
127
  1, 1, 1, 1,
124
128
  p.x, p.y, 0, 0
125
- ])
129
+ ]
126
130
  );
127
131
  }
128
132
 
@@ -133,7 +137,7 @@ function glPreRender()
133
137
  function glSetTexture(texture)
134
138
  {
135
139
  // must flush cache with the old texture to set a new one
136
- if (texture == glActiveTexture)
140
+ if (headlessMode || texture == glActiveTexture)
137
141
  return;
138
142
 
139
143
  glFlush();
@@ -193,7 +197,6 @@ function glCreateTexture(image)
193
197
  const filter = canvasPixelated ? gl_NEAREST : gl_LINEAR;
194
198
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
195
199
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, filter);
196
-
197
200
  return texture;
198
201
  }
199
202
 
@@ -217,12 +220,12 @@ function glFlush()
217
220
  }
218
221
 
219
222
  /** Draw any sprites still in the buffer, copy to main canvas and clear
220
- * @param {CanvasRenderingContext2D} context
223
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
221
224
  * @param {Boolean} [forceDraw]
222
225
  * @memberof WebGL */
223
226
  function glCopyToContext(context, forceDraw=false)
224
227
  {
225
- if (!glInstanceCount && !forceDraw) return;
228
+ if (!glEnable || !glInstanceCount && !forceDraw) return;
226
229
 
227
230
  glFlush();
228
231
 
@@ -279,7 +282,7 @@ let glPostShader, glPostTexture, glPostIncludeOverlay;
279
282
  function glInitPostProcess(shaderCode, includeOverlay=false)
280
283
  {
281
284
  ASSERT(!glPostShader, 'can only have 1 post effects shader');
282
-
285
+ if (headlessMode) return;
283
286
  if (!shaderCode) // default shader pass through
284
287
  shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
285
288
 
@@ -318,8 +321,7 @@ function glInitPostProcess(shaderCode, includeOverlay=false)
318
321
  // Render the post processing shader, called automatically by the engine
319
322
  function glRenderPostProcess()
320
323
  {
321
- if (!glPostShader)
322
- return;
324
+ if (!glPostShader || headlessMode) return;
323
325
 
324
326
  // prepare to render post process shader
325
327
  if (glEnable)
package/LittleJS.zip DELETED
Binary file