littlejsengine 1.18.15 → 1.18.18

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.
@@ -132,7 +132,7 @@ class UISystemPlugin
132
132
  let targetPos, targetSize;
133
133
  if (o.parent)
134
134
  {
135
- targetPos = o.parent.pos;
135
+ targetPos = o.parent.nativePos;
136
136
  targetSize = o.parent.size;
137
137
  }
138
138
  else
@@ -146,7 +146,7 @@ class UISystemPlugin
146
146
  }
147
147
 
148
148
  const a = o.anchor;
149
- o.pos = targetPos
149
+ o.nativePos = targetPos
150
150
  .add(targetSize.multiply(a).scale(.5)) // anchor point on target
151
151
  .subtract(o.size.multiply(a).scale(.5)) // pivot shift on self
152
152
  .add(o.localPos); // user offset
@@ -252,13 +252,18 @@ class UISystemPlugin
252
252
 
253
253
  function updateObject(o)
254
254
  {
255
- if (!o.visible) return;
255
+ if (o.destroyed || !o.visible) return;
256
256
 
257
257
  // update in reverse order to detect mouse enter/leave
258
258
  updateTransforms(o);
259
259
  for (let i=o.children.length; i--;)
260
- updateObject(o.children[i]);
261
- o.update();
260
+ {
261
+ // a child may destroy siblings mid-update (e.g. dialog close)
262
+ const child = o.children[i];
263
+ child && updateObject(child);
264
+ }
265
+ if (!o.destroyed)
266
+ o.update();
262
267
  }
263
268
  }
264
269
  function uiRender()
@@ -682,10 +687,15 @@ class UIObject
682
687
  ASSERT(isVector2(pos), 'ui object pos must be a vec2');
683
688
  ASSERT(isVector2(size), 'ui object size must be a vec2');
684
689
 
685
- /** @property {Vector2} - Local position of the object */
690
+ /** @property {Vector2} - Position you set: an offset from this object's
691
+ * anchor point (the parent box, or the canvas for roots). This is the
692
+ * input that controls placement — set this, not nativePos. */
686
693
  this.localPos = pos.copy();
687
- /** @property {Vector2} - Screen space position of the object */
688
- this.pos = pos.copy();
694
+ /** @property {Vector2} - Resolved position in native UI space, recomputed
695
+ * every frame from localPos + anchor (and nativeHeight, if set). This is a
696
+ * derived output used for drawing and hit-testing; assigning to it has no
697
+ * effect since it is overwritten each frame. Set localPos instead. */
698
+ this.nativePos = pos.copy();
689
699
  /** @property {Vector2} - Screen space size of the object */
690
700
  this.size = size.copy();
691
701
  /** @property {Color} - Color of the object */
@@ -820,7 +830,7 @@ class UIObject
820
830
  const size = !isTouchDevice ? this.size :
821
831
  this.size.add(vec2(this.extraTouchSize || 0));
822
832
  const pos = uiSystem.screenToNative(mousePosScreen);
823
- return isOverlapping(this.pos, size, pos);
833
+ return isOverlapping(this.nativePos, size, pos);
824
834
  }
825
835
 
826
836
  /** Update the object, called automatically by plugin once each frame */
@@ -910,7 +920,7 @@ class UIObject
910
920
  this.color : this.color;
911
921
  const lineWidth = this.lineWidth * (isNavigationObject ? 1.5 : 1);
912
922
 
913
- uiSystem.drawRect(this.pos, this.size, color, lineWidth, lineColor, this.cornerRadius, this.gradientColor, this.shadowColor, this.shadowBlur, this.shadowOffset);
923
+ uiSystem.drawRect(this.nativePos, this.size, color, lineWidth, lineColor, this.cornerRadius, this.gradientColor, this.shadowColor, this.shadowBlur, this.shadowOffset);
914
924
  }
915
925
 
916
926
  /** Get the size for text with overrides and scale
@@ -947,8 +957,8 @@ class UIObject
947
957
  let text = 'type = ' + this.constructor.name;
948
958
  if (this.text)
949
959
  text += '\ntext = ' + this.text;
950
- if (this.pos.x || this.pos.y)
951
- text += '\npos = ' + this.pos;
960
+ if (this.nativePos.x || this.nativePos.y)
961
+ text += '\nnativePos = ' + this.nativePos;
952
962
  if (this.localPos.x || this.localPos.y)
953
963
  text += '\nlocalPos = ' + this.localPos;
954
964
  if (this.size.x || this.size.y)
@@ -968,7 +978,7 @@ class UIObject
968
978
  this.isHoverObject() ? YELLOW :
969
979
  this.disabled ? PURPLE :
970
980
  this.interactive ? RED : BLUE;
971
- uiSystem.drawRect(this.pos, this.size, CLEAR_BLACK, 4, color);
981
+ uiSystem.drawRect(this.nativePos, this.size, CLEAR_BLACK, 4, color);
972
982
  }
973
983
 
974
984
  /** Internal function called when object is clicked
@@ -1051,7 +1061,7 @@ class UIText extends UIObject
1051
1061
 
1052
1062
  // render the text
1053
1063
  const textSize = this.getTextSize();
1054
- uiSystem.drawText(this.text, this.pos, textSize, this.textColor, this.textLineWidth, this.textLineColor, this.align, this.font, this.fontStyle, true, this.textShadow, this.shadowColor, this.shadowBlur, this.shadowOffset);
1064
+ uiSystem.drawText(this.text, this.nativePos, textSize, this.textColor, this.textLineWidth, this.textLineColor, this.align, this.font, this.fontStyle, true, this.textShadow, this.shadowColor, this.shadowBlur, this.shadowOffset);
1055
1065
  }
1056
1066
  }
1057
1067
 
@@ -1146,7 +1156,7 @@ class UITextInput extends UIObject
1146
1156
  let text = this.text;
1147
1157
  if (this.isKeyInputObject()) // add a cursor to end of text
1148
1158
  text += timeReal%1 < .5 ? '█' : '░';
1149
- uiSystem.drawText(text, this.pos, textSize,
1159
+ uiSystem.drawText(text, this.nativePos, textSize,
1150
1160
  this.textColor, this.textLineWidth, this.textLineColor, this.align, this.font, this.fontStyle, true, this.textShadow);
1151
1161
  }
1152
1162
  }
@@ -1189,7 +1199,7 @@ class UITile extends UIObject
1189
1199
  }
1190
1200
  render()
1191
1201
  {
1192
- uiSystem.drawTile(this.pos, this.size, this.tileInfo, this.color, this.angle, this.mirror, this.shadowColor, this.shadowBlur, this.shadowOffset);
1202
+ uiSystem.drawTile(this.nativePos, this.size, this.tileInfo, this.color, this.angle, this.mirror, this.shadowColor, this.shadowBlur, this.shadowOffset);
1193
1203
  }
1194
1204
  }
1195
1205
 
@@ -1228,7 +1238,7 @@ class UIButton extends UIObject
1228
1238
 
1229
1239
  // draw the text scaled to fit
1230
1240
  const textSize = this.getTextSize();
1231
- uiSystem.drawText(this.text, this.pos.add(this.textOffset), textSize,
1241
+ uiSystem.drawText(this.text, this.nativePos.add(this.textOffset), textSize,
1232
1242
  this.textColor, this.textLineWidth, this.textLineColor, this.align, this.font, this.fontStyle, true, this.textShadow);
1233
1243
  }
1234
1244
  }
@@ -1276,13 +1286,13 @@ class UICheckbox extends UIObject
1276
1286
  const p = this.cornerRadius / min(this.size.x, this.size.y) * 2;
1277
1287
  const length = lerp(1, 2**.5/2, p) / 2;
1278
1288
  let s = this.size.scale(length);
1279
- uiSystem.drawLine(this.pos.add(s.multiply(vec2(-1))), this.pos.add(s.multiply(vec2(1))), this.lineWidth, this.lineColor);
1280
- uiSystem.drawLine(this.pos.add(s.multiply(vec2(-1,1))), this.pos.add(s.multiply(vec2(1,-1))), this.lineWidth, this.lineColor);
1289
+ uiSystem.drawLine(this.nativePos.add(s.multiply(vec2(-1))), this.nativePos.add(s.multiply(vec2(1))), this.lineWidth, this.lineColor);
1290
+ uiSystem.drawLine(this.nativePos.add(s.multiply(vec2(-1,1))), this.nativePos.add(s.multiply(vec2(1,-1))), this.lineWidth, this.lineColor);
1281
1291
  }
1282
1292
 
1283
1293
  // draw the text next to the checkbox
1284
1294
  const textSize = this.getTextSize();
1285
- const pos = this.pos.add(vec2(this.size.x,0));
1295
+ const pos = this.nativePos.add(vec2(this.size.x,0));
1286
1296
  uiSystem.drawText(this.text, pos, textSize,
1287
1297
  this.textColor, this.textLineWidth, this.textLineColor, 'left', this.font, this.fontStyle, false, this.textShadow);
1288
1298
  }
@@ -1338,7 +1348,7 @@ class UISlider extends UIObject
1338
1348
  const isHorizontal = this.size.x > this.size.y;
1339
1349
  const handleSize = isHorizontal ? this.size.y : this.size.x;
1340
1350
  const barSize = isHorizontal ? this.size.x : this.size.y;
1341
- const centerPos = isHorizontal ? this.pos.x : this.pos.y;
1351
+ const centerPos = isHorizontal ? this.nativePos.x : this.nativePos.y;
1342
1352
 
1343
1353
  // check if value changed
1344
1354
  const handleWidth = barSize - handleSize;
@@ -1372,7 +1382,7 @@ class UISlider extends UIObject
1372
1382
  const minWidth = min(handleWidth, this.cornerRadius * 2);
1373
1383
  const progressWidth = lerp(minWidth, barWidth, this.value);
1374
1384
  const p = (progressWidth - barWidth) * (isHorizontal ? .5 : -.5);
1375
- const pos = this.pos.add(isHorizontal ? vec2(p, 0) : vec2(0, p));
1385
+ const pos = this.nativePos.add(isHorizontal ? vec2(p, 0) : vec2(0, p));
1376
1386
  const color = this.disabled ? this.disabledColor : this.handleColor;
1377
1387
  const drawSize = isHorizontal ?
1378
1388
  vec2(progressWidth, this.size.y) : vec2(this.size.x, progressWidth);
@@ -1383,7 +1393,7 @@ class UISlider extends UIObject
1383
1393
  // draw the slider handle
1384
1394
  const value = clamp(isHorizontal ? this.value : 1 - this.value);
1385
1395
  const p = (barWidth - handleWidth) * (value - .5);
1386
- const pos = this.pos.add(isHorizontal ? vec2(p, 0) : vec2(0, p));
1396
+ const pos = this.nativePos.add(isHorizontal ? vec2(p, 0) : vec2(0, p));
1387
1397
  const color = this.disabled ? this.disabledColor : this.handleColor;
1388
1398
  const drawSize = vec2(handleWidth);
1389
1399
  uiSystem.drawRect(pos, drawSize, color, this.lineWidth, this.lineColor, this.cornerRadius, this.gradientColor);
@@ -1391,7 +1401,7 @@ class UISlider extends UIObject
1391
1401
 
1392
1402
  // draw the text scaled to fit on the slider
1393
1403
  const textSize = this.getTextSize();
1394
- uiSystem.drawText(this.text, this.pos, textSize,
1404
+ uiSystem.drawText(this.text, this.nativePos, textSize,
1395
1405
  this.textColor, this.textLineWidth, this.textLineColor, this.align, this.font, this.fontStyle, true, this.textShadow);
1396
1406
  }
1397
1407
  navigatePressed()
@@ -1530,7 +1540,7 @@ class UIVideo extends UIObject
1530
1540
  const context = uiSystem.uiContext;
1531
1541
  const s = this.size;
1532
1542
  context.save();
1533
- context.translate(this.pos.x, this.pos.y);
1543
+ context.translate(this.nativePos.x, this.nativePos.y);
1534
1544
  context.drawImage(this.video, -s.x/2, -s.y/2, s.x, s.y);
1535
1545
  context.restore();
1536
1546
  }
package/src/engine.js CHANGED
@@ -32,7 +32,7 @@ const engineName = 'LittleJS';
32
32
  * @type {string}
33
33
  * @default
34
34
  * @memberof Engine */
35
- const engineVersion = '1.18.15';
35
+ const engineVersion = '1.18.18';
36
36
 
37
37
  /** Frames per second to update
38
38
  * @type {number}
@@ -135,7 +135,7 @@ class Sound
135
135
  * @param {number} [randomnessScale] - How much to scale pitch randomness
136
136
  * @param {boolean} [loop] - Should the sound loop?
137
137
  * @param {boolean} [paused] - Should the sound start paused
138
- * @return {SoundInstance} - The audio source node
138
+ * @return {SoundInstance} - The sound instance, or undefined if sound is disabled, not loaded, or running in headless mode
139
139
  */
140
140
  play(pos, volume=1, pitch=1, randomnessScale=1, loop=false, paused=false)
141
141
  {
@@ -166,9 +166,23 @@ class Sound
166
166
  pan = worldToScreen(pos).x * 2/mainCanvas.width - 1;
167
167
  }
168
168
 
169
- // Create and return sound instance
169
+ // Create sound instance
170
170
  const rate = pitch + pitch * this.randomness*randomnessScale*rand(-1,1);
171
- return new SoundInstance(this, volume, rate, pan, loop, paused);
171
+ const instance = new SoundInstance(this, volume, rate, pan, loop, paused);
172
+
173
+ if (debug && debugSound && pos)
174
+ {
175
+ // visualize where positioned sounds play and their falloff range
176
+ debugCircle(pos, .5, '#0ff', .5, true);
177
+ if (this.range)
178
+ {
179
+ debugCircle(pos, 2*this.range, '#0ff', .5); // silent radius
180
+ debugCircle(pos, 2*this.range*this.taper, '#0ff', .5); // full volume radius
181
+ }
182
+ debugText('vol '+volume.toFixed(2)+' pitch '+rate.toFixed(2), pos, .5, '#0ff', .5);
183
+ }
184
+
185
+ return instance;
172
186
  }
173
187
 
174
188
  /** Play a music track that loops by default
@@ -534,6 +548,10 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
534
548
  // play and return sound
535
549
  const startOffset = offset * rate;
536
550
  source.start(0, startOffset);
551
+
552
+ if (debug && debugSound)
553
+ LOG('sound', 'vol', volume.toFixed(2), 'rate', rate.toFixed(2), 'pan', pan.toFixed(2), loop ? 'loop' : '');
554
+
537
555
  return source;
538
556
  }
539
557
 
@@ -41,6 +41,7 @@ const enginePluginFiles =
41
41
  `${PLUGIN_FOLDER}/medalSystem.js`,
42
42
  `${PLUGIN_FOLDER}/newgrounds.js`,
43
43
  `${PLUGIN_FOLDER}/postProcess.js`,
44
+ `${PLUGIN_FOLDER}/lightSystem.js`,
44
45
  `${PLUGIN_FOLDER}/zzfxm.js`,
45
46
  `${PLUGIN_FOLDER}/uiSystem.js`,
46
47
  `${PLUGIN_FOLDER}/box2d.js`,
@@ -44,7 +44,7 @@ let debugKey = 'Escape';
44
44
  let debugOverlay = false;
45
45
 
46
46
  // Engine internal variables not exposed to documentation
47
- let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParticles = false, debugGamepads = false, debugTakeScreenshot;
47
+ let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParticles = false, debugGamepads = false, debugSound = false, debugTakeScreenshot;
48
48
 
49
49
  ///////////////////////////////////////////////////////////////////////////////
50
50
  // Debug helper functions
@@ -284,6 +284,8 @@ function debugUpdate()
284
284
  debugRaycast = !debugRaycast;
285
285
  if (keyWasPressed('Digit5'))
286
286
  debugScreenshot();
287
+ if (keyWasPressed('Digit7'))
288
+ debugSound = !debugSound;
287
289
  }
288
290
  if (debugVideoCaptureIsActive())
289
291
  {
@@ -303,6 +305,9 @@ function debugRender()
303
305
  // flush any gl sprites before drawing debug info
304
306
  glFlush();
305
307
 
308
+ const savedDrawCount = drawCount;
309
+ const savedPrimitiveCount = primitiveCount;
310
+
306
311
  if (debugTakeScreenshot)
307
312
  {
308
313
  // combine canvases, remove alpha and save
@@ -336,6 +341,8 @@ function debugRender()
336
341
  const stickCount = gamepadStickData[i].length;
337
342
  for (let j = 0; j < stickCount; j++)
338
343
  {
344
+ if (!(j in gamepadStickData[i]))
345
+ continue; // skip sticks that are not present (eg a disabled touch left stick)
339
346
  const stick = gamepadStick(j, i);
340
347
  const drawPos = cornerPos.add(vec2(j*stickScale*2, 0));
341
348
  const stickPos = drawPos.add(stick.scale(stickScale));
@@ -514,6 +521,8 @@ function debugRender()
514
521
  debugContext.fillStyle = '#fff';
515
522
  debugContext.fillText('5: Save Screenshot', x, y += h);
516
523
  debugContext.fillText('6: Toggle Video Capture', x, y += h);
524
+ debugContext.fillStyle = debugSound ? '#f00' : '#fff';
525
+ debugContext.fillText('7: Debug Sound', x, y += h);
517
526
 
518
527
  let keysPressed = '';
519
528
  let mousePressed = '';
@@ -548,10 +557,27 @@ function debugRender()
548
557
  debugContext.fillText(debugParticles ? 'Debug Particles' : '', x, y += h);
549
558
  debugContext.fillText(debugRaycast ? 'Debug Raycasts' : '', x, y += h);
550
559
  debugContext.fillText(debugGamepads ? 'Debug Gamepads' : '', x, y += h);
560
+ debugContext.fillText(debugSound ? 'Debug Sound' : '', x, y += h);
551
561
  }
552
562
 
553
563
  debugContext.restore();
554
564
  }
565
+
566
+ if (debugWatermark || debugOverlay)
567
+ {
568
+ // show fps stats display
569
+ mainContext.textAlign = 'right';
570
+ mainContext.textBaseline = 'top';
571
+ mainContext.font = '1em monospace';
572
+ mainContext.fillStyle = '#000';
573
+ const text = engineName + ' v' + engineVersion + ' / '
574
+ + savedDrawCount + ' / ' + savedPrimitiveCount + ' / '
575
+ + engineObjects.length + ' / ' + averageFPS.toFixed(1)
576
+ + (glEnable ? ' GL' : ' 2D') ;
577
+ mainContext.fillText(text, mainCanvas.width-3, 3);
578
+ mainContext.fillStyle = '#fff';
579
+ mainContext.fillText(text, mainCanvas.width-2, 2);
580
+ }
555
581
  }
556
582
 
557
583
  function debugRenderPost()
@@ -561,21 +587,6 @@ function debugRenderPost()
561
587
  debugVideoCaptureUpdate();
562
588
  return;
563
589
  }
564
-
565
- if (!debugWatermark && !debugOverlay) return;
566
-
567
- // update fps display
568
- mainContext.textAlign = 'right';
569
- mainContext.textBaseline = 'top';
570
- mainContext.font = '1em monospace';
571
- mainContext.fillStyle = '#000';
572
- const text = engineName + ' v' + engineVersion + ' / '
573
- + drawCount + ' / ' + primitiveCount + ' / '
574
- + engineObjects.length + ' / ' + averageFPS.toFixed(1)
575
- + (glEnable ? ' GL' : ' 2D') ;
576
- mainContext.fillText(text, mainCanvas.width-3, 3);
577
- mainContext.fillStyle = '#fff';
578
- mainContext.fillText(text, mainCanvas.width-2, 2);
579
590
  }
580
591
 
581
592
  ///////////////////////////////////////////////////////////////////////////////
package/src/engineDraw.js CHANGED
@@ -329,11 +329,12 @@ function drawTile(pos, size=vec2(1), tileInfo, color=WHITE,
329
329
  // normal canvas 2D rendering method (slower)
330
330
  ++drawCount;
331
331
  ++primitiveCount;
332
- size = new Vector2(size.x, -size.y); // flip upside down sprites
333
332
  drawCanvas2D(pos, size, angle, mirror, (context)=>
334
333
  {
335
334
  if (textureInfo)
336
335
  {
336
+ // un-flip Y so the image renders right-side up under drawCanvas2D's Y flip
337
+ context.scale(1, -1);
337
338
  // calculate uvs and render
338
339
  const x = tileInfo.pos.x, y = tileInfo.pos.y;
339
340
  const w = tileInfo.size.x, h = tileInfo.size.y;
@@ -341,7 +342,7 @@ function drawTile(pos, size=vec2(1), tileInfo, color=WHITE,
341
342
  }
342
343
  else
343
344
  {
344
- // if no tile info, use untextured rect
345
+ // if no tile info, use untextured rect (Y-symmetric, no compensation needed)
345
346
  const c = additiveColor ? color.add(additiveColor) : color;
346
347
  context.fillStyle = c.toString();
347
348
  context.fillRect(-.5, -.5, 1, 1);
@@ -415,11 +416,10 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=CLEAR_WHITE, an
415
416
  // normal canvas 2D rendering method (slower)
416
417
  ++drawCount;
417
418
  ++primitiveCount;
418
- size = new Vector2(size.x, -size.y); // fix upside down sprites
419
419
  drawCanvas2D(pos, size, angle, false, (context)=>
420
420
  {
421
- // if no tile info, use untextured rect
422
- const gradient = context.createLinearGradient(0, -.5, 0, .5);
421
+ // gradient endpoints are flipped to match the Y flip inside drawCanvas2D
422
+ const gradient = context.createLinearGradient(0, .5, 0, -.5);
423
423
  gradient.addColorStop(0, colorTop.toString());
424
424
  gradient.addColorStop(1, colorBottom.toString());
425
425
  context.fillStyle = gradient;
@@ -838,7 +838,10 @@ function drawCircleGradient(pos, size=1, colorInner=WHITE, colorOuter=CLEAR_WHIT
838
838
  * @memberof Draw
839
839
  */
840
840
 
841
- /** Draw directly to a 2d canvas context in world space
841
+ /** Draw directly to a 2d canvas context in world space.
842
+ * The Y axis is flipped so world-Y-up coordinates render right-side up
843
+ * (matches the WebGL path). Callers whose drawing depends on Y direction
844
+ * (e.g. linear gradients) should flip their own Y endpoints accordingly.
842
845
  * @param {Vector2} pos
843
846
  * @param {Vector2} size
844
847
  * @param {number} angle
@@ -1084,6 +1087,62 @@ function screenToWorldTransform(screenPos, screenSize, screenAngle=0)
1084
1087
  * @memberof Draw */
1085
1088
  function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
1086
1089
 
1090
+ /** Fit the camera to a rectangle in world space by setting cameraPos and cameraScale
1091
+ * - worldMargin pads the content rectangle in world units, so the gap scales with the content on resize
1092
+ * - screenInset reserves space in screen pixels on each viewport edge (for example a HUD band) and
1093
+ * re-centers the content away from that edge, so the reserved band stays a fixed pixel size on resize
1094
+ * - worldMargin and screenInset may each be a number for all sides, a Vector2 (x=left/right, y=top/bottom),
1095
+ * or an object with any of {top, right, bottom, left}
1096
+ * @param {Vector2} center - Center of the rectangle in world space
1097
+ * @param {Vector2} size - Size of the rectangle in world space
1098
+ * @param {number|Vector2|Object} [worldMargin] - World space padding added around the content rectangle
1099
+ * @param {number|Vector2|Object} [screenInset] - Screen space padding in pixels reserved on each viewport edge
1100
+ * @return {number} - The new camera scale
1101
+ * @memberof Draw */
1102
+ function cameraFit(center, size, worldMargin, screenInset)
1103
+ {
1104
+ ASSERT(isVector2(center), 'center must be a vec2');
1105
+ ASSERT(isVector2(size), 'size must be a vec2');
1106
+
1107
+ // pad the content
1108
+ const margin = padSides(worldMargin);
1109
+ const inset = padSides(screenInset);
1110
+ const worldW = size.x + margin.left + margin.right;
1111
+ const worldH = size.y + margin.top + margin.bottom;
1112
+ const viewW = mainCanvasSize.x - inset.left - inset.right;
1113
+ const viewH = mainCanvasSize.y - inset.top - inset.bottom;
1114
+
1115
+ // bail on a degenerate rect or viewport rather than NaN the camera
1116
+ if (!(worldW > 0 && worldH > 0 && viewW > 0 && viewH > 0))
1117
+ return cameraScale;
1118
+
1119
+ // scale to fit the padded content
1120
+ cameraScale = min(viewW / worldW, viewH / worldH);
1121
+
1122
+ // calculate offset vectors
1123
+ const marginVector = vec2(margin.right - margin.left, margin.top - margin.bottom).scale(.5);
1124
+ const insetVector = vec2(inset.right - inset.left, inset.top - inset.bottom).scale(.5 / cameraScale);
1125
+
1126
+ // apply the offsets and return camera scale
1127
+ cameraPos = center.add(marginVector).add(insetVector);
1128
+ return cameraScale;
1129
+
1130
+ function padSides(p)
1131
+ {
1132
+ // normalize a padding option to {top, right, bottom, left}
1133
+ if (p === undefined || isNumber(p))
1134
+ p = vec2(p);
1135
+ if (isVector2(p))
1136
+ return { top: p.y, right: p.x, bottom: p.y, left: p.x };
1137
+ return {
1138
+ top: p.top || 0,
1139
+ right: p.right || 0,
1140
+ bottom: p.bottom || 0,
1141
+ left: p.left || 0,
1142
+ };
1143
+ }
1144
+ }
1145
+
1087
1146
  /** Check if a box, point, or circle is on screen with a circle test
1088
1147
  * If size is a Vector2, uses the length as diameter
1089
1148
  * This can be used to cull offscreen objects from render or update
@@ -1122,14 +1181,13 @@ function isOnScreen(pos, size=0)
1122
1181
  y + size > -h && y - size < h;
1123
1182
  }
1124
1183
 
1125
- /** Enable normal or additive blend mode
1184
+ /** Enable additive blending
1126
1185
  * @param {boolean} [additive]
1127
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
1128
1186
  * @memberof Draw */
1129
- function setBlendMode(additive=false, context=drawContext)
1187
+ function setAdditiveBlendMode(additive=true)
1130
1188
  {
1131
1189
  glAdditive = additive;
1132
- context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
1190
+ drawContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
1133
1191
  }
1134
1192
 
1135
1193
  /** Combines LittleJS canvases onto the main canvas
@@ -85,12 +85,18 @@ export
85
85
  inputWASDEmulateDirection,
86
86
  touchInputEnable,
87
87
  touchGamepadEnable,
88
+ touchGamepadPassthrough,
88
89
  touchGamepadCenterButtonSize,
89
90
  touchGamepadButtonCount,
91
+ touchGamepadLeftStick,
92
+ touchGamepadLeftButtonCount,
93
+ touchGamepadRightStick,
90
94
  touchGamepadAnalog,
95
+ touchGamepadFloating,
91
96
  touchGamepadSize,
92
97
  touchGamepadAlpha,
93
98
  touchGamepadDisplayTime,
99
+ touchGamepadVibration,
94
100
  vibrateEnable,
95
101
  soundEnable,
96
102
  soundVolume,
@@ -133,12 +139,18 @@ export
133
139
  setGamepadDirectionEmulateStick,
134
140
  setInputWASDEmulateDirection,
135
141
  setTouchGamepadEnable,
142
+ setTouchGamepadPassthrough,
136
143
  setTouchGamepadCenterButtonSize,
137
144
  setTouchGamepadButtonCount,
145
+ setTouchGamepadLeftStick,
146
+ setTouchGamepadLeftButtonCount,
147
+ setTouchGamepadRightStick,
138
148
  setTouchGamepadAnalog,
149
+ setTouchGamepadFloating,
139
150
  setTouchGamepadSize,
140
151
  setTouchGamepadAlpha,
141
152
  setTouchGamepadDisplayTime,
153
+ setTouchGamepadVibration,
142
154
  setVibrateEnable,
143
155
  setSoundEnable,
144
156
  setSoundVolume,
@@ -264,7 +276,7 @@ export
264
276
  drawCanvas2D,
265
277
  drawText,
266
278
  drawTextScreen,
267
- setBlendMode,
279
+ setAdditiveBlendMode,
268
280
  combineCanvases,
269
281
  engineImageFont,
270
282
  ImageFont,
@@ -272,6 +284,7 @@ export
272
284
  toggleFullscreen,
273
285
  setCursor,
274
286
  getCameraSize,
287
+ cameraFit,
275
288
  isOnScreen,
276
289
 
277
290
  // WebGL
@@ -315,10 +328,16 @@ export
315
328
  mouseWheel,
316
329
  mouseInWindow,
317
330
  isUsingGamepad,
331
+ lastInputDevice,
332
+ inputMouseMoveThreshold,
318
333
  inputPreventDefault,
319
334
  gamepadPrimary,
320
335
  isTouchDevice,
321
336
  setInputPreventDefault,
337
+ setInputMouseMoveThreshold,
338
+ usingMouseInput,
339
+ usingKeyboardInput,
340
+ usingGamepadInput,
322
341
  gamepadIsDown,
323
342
  gamepadWasPressed,
324
343
  gamepadWasReleased,