littlejsengine 1.18.25 → 1.18.27

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.
package/src/engineDraw.js CHANGED
@@ -57,6 +57,12 @@ let workReadCanvas;
57
57
  * @memberof Draw */
58
58
  let workReadContext;
59
59
 
60
+ /** Extra canvas to composite behind the engine canvases when combining canvases
61
+ * Set by plugins that render to their own canvas below the LittleJS canvases
62
+ * @type {HTMLCanvasElement}
63
+ * @memberof Draw */
64
+ let backgroundCanvas;
65
+
60
66
  /** The size of the main canvas (and other secondary canvases)
61
67
  * @type {Vector2}
62
68
  * @memberof Draw */
@@ -1208,6 +1214,13 @@ function setAdditiveBlendMode(additive=true)
1208
1214
  drawContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
1209
1215
  }
1210
1216
 
1217
+ /** Set an extra canvas to composite behind the engine canvases when combining
1218
+ * Plugins that insert their own canvas below the LittleJS canvases should set
1219
+ * this so it appears in screenshots and video capture
1220
+ * @param {HTMLCanvasElement} [canvas]
1221
+ * @memberof Draw */
1222
+ function setBackgroundCanvas(canvas) { backgroundCanvas = canvas; }
1223
+
1211
1224
  /** Combines LittleJS canvases onto the main canvas
1212
1225
  * This is necessary for things like screenshots and video
1213
1226
  * @memberof Draw */
@@ -1220,6 +1233,8 @@ function combineCanvases()
1220
1233
  // leaving workContext.fillStyle transparent can't silently no-op this
1221
1234
  workContext.fillStyle = '#000';
1222
1235
  workContext.fillRect(0,0,w,h);
1236
+ if (backgroundCanvas)
1237
+ workContext.drawImage(backgroundCanvas, 0, 0, w, h);
1223
1238
  glCopyToContext(workContext);
1224
1239
  workContext.drawImage(mainCanvas, 0, 0);
1225
1240
  mainContext.drawImage(workCanvas, 0, 0);
@@ -1463,9 +1478,15 @@ class ImageFont
1463
1478
  tileInfo.pos.x = x*sizePaddedX + padding;
1464
1479
  tileInfo.pos.y = y*sizePaddedY + padding;
1465
1480
 
1466
- // draw the tile
1467
- drawPos.x = pos.x + i * size.x - centerOffset |0;
1468
- drawPos.y = pos.y + j * size.y |0;
1481
+ // snap the glyph edges to whole pixels
1482
+ // tiles are drawn from their center, so snapping the center
1483
+ // to a whole pixel puts the edges on half pixels when the
1484
+ // size is even, and a row or column of the glyph then has
1485
+ // no pixel center inside it and is not rasterized at all
1486
+ // ceil picks the nearest aligned position, breaking ties
1487
+ // downward to match how this used to truncate
1488
+ drawPos.x = ceil(pos.x + i * size.x - centerOffset - size.x/2) + size.x/2 - .5;
1489
+ drawPos.y = ceil(pos.y + j * size.y - size.y/2) + size.y/2 - .5;
1469
1490
  drawTile(drawPos, size, tileInfo, color, 0, false, undefined, useWebGL, true, context);
1470
1491
  }
1471
1492
  });
@@ -84,6 +84,7 @@ export
84
84
  glCircleSides,
85
85
  gamepadsEnable,
86
86
  gamepadDirectionEmulateStick,
87
+ gamepadAxisFilterEnable,
87
88
  inputWASDEmulateDirection,
88
89
  touchInputEnable,
89
90
  touchGamepadEnable,
@@ -140,6 +141,7 @@ export
140
141
  setTouchInputEnable,
141
142
  setGamepadsEnable,
142
143
  setGamepadDirectionEmulateStick,
144
+ setGamepadAxisFilterEnable,
143
145
  setInputWASDEmulateDirection,
144
146
  setTouchGamepadEnable,
145
147
  setTouchGamepadPassthrough,
@@ -255,6 +257,7 @@ export
255
257
  workContext,
256
258
  workReadCanvas,
257
259
  workReadContext,
260
+ backgroundCanvas,
258
261
  mainCanvasSize,
259
262
  textureInfos,
260
263
  drawCount,
@@ -280,6 +283,7 @@ export
280
283
  drawText,
281
284
  drawTextScreen,
282
285
  setAdditiveBlendMode,
286
+ setBackgroundCanvas,
283
287
  combineCanvases,
284
288
  engineImageFont,
285
289
  ImageFont,
@@ -124,6 +124,7 @@ function inputClear()
124
124
  touchGamepadStickPointerId.length = 0; // release floating sticks so they re-anchor
125
125
  gamepadStickData.length = 0;
126
126
  gamepadDpadData.length = 0;
127
+ gamepadAxisCentered.length = 0;
127
128
  }
128
129
 
129
130
  ///////////////////////////////////////////////////////////////////////////////
@@ -361,6 +362,11 @@ const inputData = [[]];
361
362
 
362
363
  // gamepad internal variables
363
364
  const gamepadStickData = [], gamepadDpadData = [], gamepadHadInput = [];
365
+ // per gamepad, how many consecutive frames each axis has rested inside the
366
+ // dead zone, used to tell stick axes from axes that rest at full deflection
367
+ const gamepadAxisCentered = [];
368
+ // how long an axis must rest inside the dead zone before it counts as a stick
369
+ const gamepadAxisCenteredFrames = 15;
364
370
 
365
371
  // touch gamepad internal variables
366
372
  const touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadSticks = [];
@@ -609,7 +615,7 @@ function inputUpdate()
609
615
  // gamepad: any button held or stick moved
610
616
  let gamepadActive = false;
611
617
  for (let s = gamepadStickCount(); s-- && !gamepadActive;)
612
- gamepadActive = gamepadStick(s).lengthSquared() > .04;
618
+ gamepadActive = gamepadStick(s).lengthSquared() > .2;
613
619
  for (let b = 17; b-- && !gamepadActive;)
614
620
  gamepadActive = gamepadIsDown(b);
615
621
 
@@ -637,12 +643,12 @@ function inputUpdate()
637
643
  // gamepads are updated by engine every frame automatically
638
644
  function gamepadsUpdate()
639
645
  {
646
+ const deadZoneMin=.3, deadZoneMax=.8;
640
647
  const applyDeadZones = (v)=>
641
648
  {
642
- const min=.3, max=.8;
643
649
  const deadZone = (v)=>
644
- v > min ? percent(v, min, max) :
645
- v < -min ? -percent(-v, min, max) : 0;
650
+ v > deadZoneMin ? percent(v, deadZoneMin, deadZoneMax) :
651
+ v < -deadZoneMin ? -percent(-v, deadZoneMin, deadZoneMax) : 0;
646
652
  return vec2(deadZone(v.x), deadZone(-v.y)).clampLength();
647
653
  };
648
654
 
@@ -726,6 +732,7 @@ function inputUpdate()
726
732
  gamepadStickData[i] = undefined;
727
733
  gamepadDpadData[i] = undefined;
728
734
  gamepadHadInput[i] = undefined;
735
+ gamepadAxisCentered[i] = undefined;
729
736
  continue;
730
737
  }
731
738
 
@@ -734,8 +741,30 @@ function inputUpdate()
734
741
  const dpad = gamepadDpadData[i] ?? (gamepadDpadData[i] = vec2());
735
742
 
736
743
  // read analog sticks
744
+ // gamepads without standard mapping (steering wheels, flight sticks)
745
+ // can report axes that rest at full deflection instead of center,
746
+ // which would otherwise read as a stick held down forever, so only
747
+ // trust an axis once it has rested inside the dead zone for a moment
748
+ const isStandard = gamepad.mapping === 'standard';
749
+ const centered = gamepadAxisCentered[i] ?? (gamepadAxisCentered[i] = []);
750
+ const readAxis = (j)=>
751
+ {
752
+ const v = gamepad.axes[j];
753
+ if (isStandard && j < 4)
754
+ return v; // spec guarantees axes 0-3 are the two sticks
755
+ if (!gamepadAxisFilterEnable)
756
+ return v;
757
+
758
+ // once an axis has proven it rests at center it stays trusted,
759
+ // otherwise moving it would immediately disqualify it again
760
+ const frames = centered[j] | 0;
761
+ if (frames > gamepadAxisCenteredFrames)
762
+ return v;
763
+ centered[j] = abs(v) < deadZoneMin ? frames + 1 : 0;
764
+ return 0;
765
+ };
737
766
  for (let j = 0; j < gamepad.axes.length-1; j+=2)
738
- sticks[j>>1] = applyDeadZones(vec2(gamepad.axes[j],gamepad.axes[j+1]));
767
+ sticks[j>>1] = applyDeadZones(vec2(readAxis(j), readAxis(j+1)));
739
768
 
740
769
  // read buttons
741
770
  let hadInput = false;
@@ -236,6 +236,14 @@ let gamepadsEnable = true;
236
236
  * @memberof Settings */
237
237
  let gamepadDirectionEmulateStick = true;
238
238
 
239
+ /** If true, axes that do not rest near center are ignored on gamepads without
240
+ * standard mapping. Steering wheels and flight sticks report pedal and throttle
241
+ * axes that rest at full deflection, which otherwise reads as a stick held down.
242
+ * @type {boolean}
243
+ * @default
244
+ * @memberof Settings */
245
+ let gamepadAxisFilterEnable = true;
246
+
239
247
  /** If true the WASD keys are also routed to the direction keys (for better accessibility)
240
248
  * @type {boolean}
241
249
  * @default
@@ -574,6 +582,11 @@ function setGamepadsEnable(enable) { gamepadsEnable = enable; }
574
582
  * @memberof Settings */
575
583
  function setGamepadDirectionEmulateStick(enable) { gamepadDirectionEmulateStick = enable; }
576
584
 
585
+ /** Set if axes that do not rest near center are ignored on non-standard gamepads
586
+ * @param {boolean} enable
587
+ * @memberof Settings */
588
+ function setGamepadAxisFilterEnable(enable) { gamepadAxisFilterEnable = enable; }
589
+
577
590
  /** Set if true the WASD keys are also routed to the direction keys
578
591
  * @param {boolean} enable
579
592
  * @memberof Settings */