littlejsengine 1.21.0 → 1.23.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.
- package/README.md +1 -1
- package/dist/littlejs.d.ts +1258 -141
- package/dist/littlejs.esm.js +5023 -429
- package/dist/littlejs.esm.min.js +22 -9
- package/dist/littlejs.js +4977 -430
- package/dist/littlejs.min.js +21 -8
- package/dist/littlejs.release.js +2759 -313
- package/package.json +3 -2
- package/plugins/gltf.js +7 -1
- package/plugins/lightSystem.js +50 -5
- package/plugins/math3d.js +42 -20
- package/plugins/parallax.js +174 -0
- package/plugins/particleEffects.js +760 -0
- package/plugins/pluginExport.js +40 -0
- package/plugins/postProcess.js +206 -28
- package/plugins/render3d.js +495 -188
- package/plugins/render3dEditor.js +1754 -80
- package/plugins/render3dExtras.js +294 -21
- package/plugins/render3dLevel.js +463 -6
- package/plugins/sceneSystem.js +3 -2
- package/src/engine.js +1 -1
- package/src/engineAudio.js +25 -8
- package/src/engineBuild.mjs +2 -0
- package/src/engineDraw.js +59 -4
- package/src/engineEditor.js +475 -37
- package/src/engineExport.js +7 -0
- package/src/engineInput.js +48 -6
- package/src/engineParticles.js +80 -11
- package/src/engineRelease.js +13 -2
- package/src/engineSettings.js +14 -0
- package/src/engineWebGL.js +27 -10
package/src/engineExport.js
CHANGED
|
@@ -36,6 +36,8 @@ export
|
|
|
36
36
|
debugKeysAlways,
|
|
37
37
|
debugTweakables,
|
|
38
38
|
levelEditor,
|
|
39
|
+
LevelEditor,
|
|
40
|
+
setLevelEditor,
|
|
39
41
|
|
|
40
42
|
// Debug
|
|
41
43
|
ASSERT,
|
|
@@ -97,6 +99,7 @@ export
|
|
|
97
99
|
touchInputEnable,
|
|
98
100
|
touchGamepadEnable,
|
|
99
101
|
touchGamepadPassthrough,
|
|
102
|
+
touchPinchWheel,
|
|
100
103
|
touchGamepadCenterButtonSize,
|
|
101
104
|
touchGamepadButtonCount,
|
|
102
105
|
touchGamepadLeftStick,
|
|
@@ -158,6 +161,7 @@ export
|
|
|
158
161
|
setInputWASDEmulateDirection,
|
|
159
162
|
setTouchGamepadEnable,
|
|
160
163
|
setTouchGamepadPassthrough,
|
|
164
|
+
setTouchPinchWheel,
|
|
161
165
|
setTouchGamepadCenterButtonSize,
|
|
162
166
|
setTouchGamepadButtonCount,
|
|
163
167
|
setTouchGamepadLeftStick,
|
|
@@ -335,6 +339,8 @@ export
|
|
|
335
339
|
glFlush,
|
|
336
340
|
glCopyToContext,
|
|
337
341
|
glSetAntialias,
|
|
342
|
+
glMaxTextureSize,
|
|
343
|
+
glClampTextureSize,
|
|
338
344
|
glDraw,
|
|
339
345
|
glDrawUntextured,
|
|
340
346
|
glDrawPointsTransform,
|
|
@@ -359,6 +365,7 @@ export
|
|
|
359
365
|
mouseDelta,
|
|
360
366
|
mouseDeltaScreen,
|
|
361
367
|
mouseWheel,
|
|
368
|
+
touchPinch,
|
|
362
369
|
mouseInWindow,
|
|
363
370
|
isUsingGamepad,
|
|
364
371
|
lastInputDevice,
|
package/src/engineInput.js
CHANGED
|
@@ -38,6 +38,13 @@ let mouseDeltaScreen = vec2();
|
|
|
38
38
|
* @memberof Input */
|
|
39
39
|
let mouseWheel = 0;
|
|
40
40
|
|
|
41
|
+
/** Touch pinch delta this frame, in the mouse wheel's units: two fingers moving apart are negative, as the wheel
|
|
42
|
+
* turned up is, and moving together positive, 1 for every 50 pixels; added to mouseWheel while touchPinchWheel
|
|
43
|
+
* is on, so a game that zooms with the wheel zooms with a pinch
|
|
44
|
+
* @type {number}
|
|
45
|
+
* @memberof Input */
|
|
46
|
+
let touchPinch = 0;
|
|
47
|
+
|
|
41
48
|
/** True if mouse was inside the document window, set to false when mouse leaves
|
|
42
49
|
* @type {boolean}
|
|
43
50
|
* @memberof Input */
|
|
@@ -126,6 +133,7 @@ function inputClearKey(key, device=0, clearDown=true, clearPressed=true, clearRe
|
|
|
126
133
|
const inputWASDToArrow = {KeyW:'ArrowUp', KeyS:'ArrowDown', KeyA:'ArrowLeft', KeyD:'ArrowRight'};
|
|
127
134
|
const inputArrowToWASD = {ArrowUp:'KeyW', ArrowDown:'KeyS', ArrowLeft:'KeyA', ArrowRight:'KeyD'};
|
|
128
135
|
const inputKeysHeld = new Set; // the keys physically down, since an arrow's slot is shared with its alias
|
|
136
|
+
let inputPinch; // the two fingers pinching and how far apart they were, undefined with no pinch
|
|
129
137
|
let inputWasTouching = 0, inputTouchIdentifier; // the touch driving the mouse, cleared with the input so only a new touch presses
|
|
130
138
|
let inputLastTouchTime = -1e9; // when a touch event last came, the mouse events a browser makes from a tap follow it
|
|
131
139
|
|
|
@@ -168,6 +176,7 @@ function inputClear()
|
|
|
168
176
|
if (!i || inputData[i]) inputData[i] = [];
|
|
169
177
|
inputKeysHeld.clear();
|
|
170
178
|
inputWasTouching = 0;
|
|
179
|
+
inputPinch = undefined;
|
|
171
180
|
// release the touch gamepad, a finger still on it has to lift before it can take a control again
|
|
172
181
|
touchGamepadPointerRole.clear();
|
|
173
182
|
touchGamepadButtons.length = 0;
|
|
@@ -695,19 +704,52 @@ function inputInit()
|
|
|
695
704
|
const pressTouch = !inputWasTouching && e.type == 'touchstart' &&
|
|
696
705
|
[...e.changedTouches].find(t=> !isGamepadTouch(t));
|
|
697
706
|
const touch = pressTouch || gameTouches[0];
|
|
707
|
+
let pos = vec2(touch.clientX, touch.clientY);
|
|
708
|
+
|
|
709
|
+
// two fingers pinch: how far they moved together or apart is the touch screen's wheel, a
|
|
710
|
+
// click for every 50 pixels, and the mouse is between them, the place to zoom about; with
|
|
711
|
+
// touchPinchWheel off the pinch is only measured, and the first finger is the mouse as ever
|
|
712
|
+
const pinching = touching > 1 && touchPinchWheel;
|
|
713
|
+
if (touching > 1)
|
|
714
|
+
{
|
|
715
|
+
const [a, b] = gameTouches, key = a.identifier + ' ' + b.identifier;
|
|
716
|
+
const distance = hypot(a.clientX - b.clientX, a.clientY - b.clientY);
|
|
717
|
+
if (inputPinch && inputPinch.key === key)
|
|
718
|
+
{
|
|
719
|
+
const delta = (inputPinch.distance - distance) / 50;
|
|
720
|
+
touchPinch += delta;
|
|
721
|
+
if (touchPinchWheel)
|
|
722
|
+
mouseWheel += delta;
|
|
723
|
+
}
|
|
724
|
+
inputPinch = {key, distance}; // other fingers start it over, with no jump
|
|
725
|
+
if (pinching)
|
|
726
|
+
pos = vec2((a.clientX + b.clientX) / 2, (a.clientY + b.clientY) / 2);
|
|
727
|
+
}
|
|
728
|
+
else
|
|
729
|
+
inputPinch = undefined;
|
|
698
730
|
|
|
699
731
|
// set event pos and pass it along
|
|
700
|
-
const pos = vec2(touch.clientX, touch.clientY);
|
|
701
732
|
const mousePosScreenLast = mousePosScreen;
|
|
702
733
|
mousePosScreen = mouseEventToScreen(pos);
|
|
703
|
-
if (
|
|
734
|
+
if (pinching)
|
|
735
|
+
{
|
|
736
|
+
// a pinch is not a press or a drag, the finger that pressed lets go
|
|
737
|
+
if (inputData[0][button] & 1)
|
|
738
|
+
inputData[0][button] = inputData[0][button] & 2 | 4;
|
|
739
|
+
}
|
|
740
|
+
else if (inputWasTouching && touch.identifier === inputTouchIdentifier)
|
|
704
741
|
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
705
742
|
else if (pressTouch)
|
|
706
743
|
inputData[0][button] = 3;
|
|
707
|
-
|
|
744
|
+
// the finger left after a pinch moves the mouse from where it is, with no jump and no press
|
|
745
|
+
inputTouchIdentifier = pinching ? undefined : touch.identifier;
|
|
746
|
+
}
|
|
747
|
+
else
|
|
748
|
+
{
|
|
749
|
+
inputPinch = undefined;
|
|
750
|
+
if (inputWasTouching && (inputData[0][button] & 1))
|
|
751
|
+
inputData[0][button] = inputData[0][button] & 2 | 4; // released only if it was pressed
|
|
708
752
|
}
|
|
709
|
-
else if (inputWasTouching && (inputData[0][button] & 1))
|
|
710
|
-
inputData[0][button] = inputData[0][button] & 2 | 4; // released only if it was pressed
|
|
711
753
|
|
|
712
754
|
// set was touching
|
|
713
755
|
inputWasTouching = touching;
|
|
@@ -966,7 +1008,7 @@ function inputUpdatePost()
|
|
|
966
1008
|
for (const deviceInputData of inputData)
|
|
967
1009
|
for (const i in deviceInputData)
|
|
968
1010
|
deviceInputData[i] &= 1;
|
|
969
|
-
mouseWheel = 0;
|
|
1011
|
+
mouseWheel = touchPinch = 0;
|
|
970
1012
|
mouseDelta = vec2();
|
|
971
1013
|
mouseDeltaScreen = vec2();
|
|
972
1014
|
}
|
package/src/engineParticles.js
CHANGED
|
@@ -165,6 +165,13 @@ class ParticleEmitter extends EngineObject
|
|
|
165
165
|
this.localSpace = localSpace;
|
|
166
166
|
/** @property {number} - If non zero the particle is drawn as a trail, stretched in the direction of velocity */
|
|
167
167
|
this.trailScale = 0;
|
|
168
|
+
/** @property {number} - Grows the whole effect as each particle is born: the spawn area, the particles' sizes,
|
|
169
|
+
* speed and fall, so it looks the same only bigger; 1 is as made, and a particle keeps the scale it was born
|
|
170
|
+
* with, so a change never moves the ones already out */
|
|
171
|
+
this.scale = 1;
|
|
172
|
+
/** @property {number} - The effect's own fall, added to each particle's velocity y every frame, on top of the
|
|
173
|
+
* world's gravity times gravityScale; an effect that sets its own looks the same in any game */
|
|
174
|
+
this.gravity = 0;
|
|
168
175
|
/** @property {ParticleCallback|undefined} - Callback when particle is created
|
|
169
176
|
* @type {ParticleCallback|undefined} */
|
|
170
177
|
this.particleCreateCallback = undefined;
|
|
@@ -251,9 +258,9 @@ class ParticleEmitter extends EngineObject
|
|
|
251
258
|
{
|
|
252
259
|
// show emitter bounds
|
|
253
260
|
if (this.emitCircle)
|
|
254
|
-
debugCircle(this.pos, this.emitSize.x, '#0f0', 0, false, false);
|
|
261
|
+
debugCircle(this.pos, this.emitSize.x * this.scale, '#0f0', 0, false, false);
|
|
255
262
|
else
|
|
256
|
-
debugRect(this.pos, this.emitSize, '#0f0', 0, this.angle, false, false);
|
|
263
|
+
debugRect(this.pos, this.emitSize.scale(this.scale), '#0f0', 0, this.angle, false, false);
|
|
257
264
|
}
|
|
258
265
|
}
|
|
259
266
|
|
|
@@ -262,10 +269,11 @@ class ParticleEmitter extends EngineObject
|
|
|
262
269
|
emitParticle()
|
|
263
270
|
{
|
|
264
271
|
// spawn a particle
|
|
272
|
+
const scale = this.scale; // grows the spawn area, the sizes and the speed of each particle born
|
|
265
273
|
let pos = this.emitCircle ? // check if circle emitter
|
|
266
|
-
randInCircle(this.emitSize.x/2)
|
|
267
|
-
: vec2(rand(-.5,.5),
|
|
268
|
-
|
|
274
|
+
randInCircle(this.emitSize.x/2 * scale) // circle emitter
|
|
275
|
+
: vec2(rand(-.5,.5) * this.emitSize.x * scale, // box emitter, one vector made
|
|
276
|
+
rand(-.5,.5) * this.emitSize.y * scale);
|
|
269
277
|
let angle = rand(this.particleConeAngle, -this.particleConeAngle);
|
|
270
278
|
if (!this.localSpace)
|
|
271
279
|
{
|
|
@@ -282,9 +290,9 @@ class ParticleEmitter extends EngineObject
|
|
|
282
290
|
|
|
283
291
|
// randomize particle settings
|
|
284
292
|
const particleTime = randomizeScale(this.particleTime);
|
|
285
|
-
const sizeStart = randomizeScale(this.sizeStart);
|
|
286
|
-
const sizeEnd = randomizeScale(this.sizeEnd);
|
|
287
|
-
const speed = randomizeScale(this.speed);
|
|
293
|
+
const sizeStart = randomizeScale(this.sizeStart) * scale;
|
|
294
|
+
const sizeEnd = randomizeScale(this.sizeEnd) * scale;
|
|
295
|
+
const speed = randomizeScale(this.speed) * scale;
|
|
288
296
|
const angleSpeed = randomizeScale(this.angleSpeed) * randSign();
|
|
289
297
|
const coneAngle = rand(this.emitConeAngle, -this.emitConeAngle);
|
|
290
298
|
const colorStart = randColor(this.colorStartA, this.colorStartB, this.randomColorLinear);
|
|
@@ -318,9 +326,13 @@ class ParticleEmitter extends EngineObject
|
|
|
318
326
|
render()
|
|
319
327
|
{
|
|
320
328
|
// render all particles, the blend switched once for them all, not around each one
|
|
329
|
+
// and the tile's draw worked out once for them all, fresh each frame so a tile changed in place draws changed
|
|
321
330
|
this.additive && setAdditiveBlendMode();
|
|
331
|
+
particleTile = null; // none yet, undefined is the untextured tile
|
|
332
|
+
particleTileBatch = true;
|
|
322
333
|
for (const particle of this.particles)
|
|
323
334
|
particle.render();
|
|
335
|
+
particleTileBatch = false;
|
|
324
336
|
this.additive && setAdditiveBlendMode(false);
|
|
325
337
|
}
|
|
326
338
|
|
|
@@ -347,6 +359,39 @@ class ParticleEmitter extends EngineObject
|
|
|
347
359
|
// scratch vectors reused by Particle.render and the tile collision to avoid per-frame allocations
|
|
348
360
|
const particleDrawPos = new Vector2, particleDrawSize = new Vector2, particleCollidePos = new Vector2;
|
|
349
361
|
|
|
362
|
+
// the WebGL draw of the tile particles draw with, what drawTile works out for each sprite: kept while an emitter
|
|
363
|
+
// renders its particles, so each one only writes its own values into the batch
|
|
364
|
+
let particleTile, particleTileBatch = false, particleTileTextured = false, particleTileEmpty = false;
|
|
365
|
+
let particleTileU0 = 0, particleTileV0 = 0, particleTileU1 = 0, particleTileV1 = 0;
|
|
366
|
+
function particleTileSet(tileInfo)
|
|
367
|
+
{
|
|
368
|
+
particleTile = tileInfo;
|
|
369
|
+
const textureInfo = tileInfo?.textureInfo;
|
|
370
|
+
particleTileTextured = !!textureInfo;
|
|
371
|
+
particleTileEmpty = !!textureInfo && !(tileInfo.size.x && tileInfo.size.y); // like a sprite still loading
|
|
372
|
+
if (!textureInfo || particleTileEmpty) return;
|
|
373
|
+
|
|
374
|
+
ASSERT(!!textureInfo.glTexture, 'texture has no WebGL texture, draw it with useWebGL false');
|
|
375
|
+
glSetTexture(textureInfo.glTexture); // bound, and its mipmaps brought up to date, once for the emitter
|
|
376
|
+
const sizeInverse = textureInfo.sizeInverse, bleed = tileInfo.bleed;
|
|
377
|
+
const x = tileInfo.pos.x * sizeInverse.x;
|
|
378
|
+
const y = tileInfo.pos.y * sizeInverse.y;
|
|
379
|
+
const w = tileInfo.size.x * sizeInverse.x;
|
|
380
|
+
const h = tileInfo.size.y * sizeInverse.y;
|
|
381
|
+
if (bleed)
|
|
382
|
+
{
|
|
383
|
+
const bleedX = sizeInverse.x*bleed;
|
|
384
|
+
const bleedY = sizeInverse.y*bleed;
|
|
385
|
+
particleTileU0 = x + bleedX; particleTileV0 = y + bleedY;
|
|
386
|
+
particleTileU1 = x - bleedX + w; particleTileV1 = y - bleedY + h;
|
|
387
|
+
}
|
|
388
|
+
else
|
|
389
|
+
{
|
|
390
|
+
particleTileU0 = x; particleTileV0 = y;
|
|
391
|
+
particleTileU1 = x + w; particleTileV1 = y + h;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
350
395
|
// tests if a particle collides with tiles at x, y, through its emitter's collide callback if it has one
|
|
351
396
|
function particleCollideTest(particle, collideCallback, x, y)
|
|
352
397
|
{
|
|
@@ -382,6 +427,8 @@ class Particle
|
|
|
382
427
|
{
|
|
383
428
|
/** @property {ParticleEmitter} - The emitter this particle came from */
|
|
384
429
|
this.emitter = emitter;
|
|
430
|
+
/** @property {number} - The emitter's scale when it was made, which grows its fall */
|
|
431
|
+
this.scale = emitter.scale ?? 1; // a hand made emitter-like object may have none
|
|
385
432
|
/** @property {Vector2} - Position, world space or local to the emitter when localSpace is set */
|
|
386
433
|
this.pos = pos;
|
|
387
434
|
/** @property {number} - Angle in radians */
|
|
@@ -444,7 +491,10 @@ class Particle
|
|
|
444
491
|
// apply physics; only the tile collision needs where the particle was
|
|
445
492
|
const solve = enablePhysicsSolver && collideLevel;
|
|
446
493
|
const oldX = this.pos.x, oldY = this.pos.y;
|
|
447
|
-
|
|
494
|
+
// the world's gravity times gravityScale plus the emitter's own, grown by the particle's scale
|
|
495
|
+
const fall = this.scale;
|
|
496
|
+
let gravityX = gravity.x * gravityScale * fall;
|
|
497
|
+
let gravityY = (gravity.y * gravityScale + (emitter.gravity || 0)) * fall;
|
|
448
498
|
if (emitter.localSpace && emitter.angle)
|
|
449
499
|
{
|
|
450
500
|
// world gravity turned into the emitter's space, the render turns it back
|
|
@@ -498,7 +548,9 @@ class Particle
|
|
|
498
548
|
}
|
|
499
549
|
if (isBlockedY || !isBlockedX)
|
|
500
550
|
{
|
|
501
|
-
|
|
551
|
+
// down is the world's gravity, or the emitter's own fall in a world with none
|
|
552
|
+
const down = gravity.y || emitter.gravity || 0;
|
|
553
|
+
const wasFalling = this.velocity.y < 0 && down < 0 || this.velocity.y > 0 && down > 0;
|
|
502
554
|
if (wasFalling)
|
|
503
555
|
this.groundObject = hitLayer;
|
|
504
556
|
|
|
@@ -577,7 +629,24 @@ class Particle
|
|
|
577
629
|
angle = atan2(velocity.x, velocity.y);
|
|
578
630
|
}
|
|
579
631
|
}
|
|
580
|
-
|
|
632
|
+
if (glEnable && !headlessMode)
|
|
633
|
+
{
|
|
634
|
+
// straight into the batch, the tile's draw worked out once for the emitter's particles
|
|
635
|
+
const tileInfo = this.tileInfo;
|
|
636
|
+
if (!particleTileBatch || tileInfo !== particleTile)
|
|
637
|
+
particleTileSet(tileInfo);
|
|
638
|
+
if (!particleTileTextured)
|
|
639
|
+
glDrawUntextured(pos.x, pos.y, size.x, size.y, angle, this.color.rgbaInt());
|
|
640
|
+
else if (!particleTileEmpty)
|
|
641
|
+
{
|
|
642
|
+
const texture = tileInfo.textureInfo.glTexture;
|
|
643
|
+
glActiveTexture === texture || glSetTexture(texture); // bound again only if a draw between changed it
|
|
644
|
+
glDraw(pos.x, pos.y, this.mirror ? -size.x : size.x, size.y, angle,
|
|
645
|
+
particleTileU0, particleTileV0, particleTileU1, particleTileV1, this.color.rgbaInt());
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
else
|
|
649
|
+
drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror, undefined, false, false);
|
|
581
650
|
additive && setAdditiveBlendMode(false);
|
|
582
651
|
debugParticles && debugRect(pos, size, '#f005', 0, angle, false, false);
|
|
583
652
|
}
|
package/src/engineRelease.js
CHANGED
|
@@ -52,8 +52,19 @@ function tweak(){}
|
|
|
52
52
|
function tweakButton(){}
|
|
53
53
|
function tweakDivider(){}
|
|
54
54
|
function tweakEngineDefaults(){}
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
class LevelEditor
|
|
56
|
+
{
|
|
57
|
+
constructor() { this.paletteTiles = this.use3D = this.tool = undefined; }
|
|
58
|
+
get isOpen() { return false; }
|
|
59
|
+
get is3D() { return false; }
|
|
60
|
+
get edit2D() { return undefined; }
|
|
61
|
+
get edit3D() { return undefined; }
|
|
62
|
+
open(){} close(){} addKey(){} addButton(){} addTool(){}
|
|
63
|
+
onTile(){} onRestart(){} onPlayFrom(){} onOpen(){} onClose(){} onUpdate(){} onDraw(){} onPanel(){}
|
|
64
|
+
onSave(){ return false; }
|
|
65
|
+
}
|
|
66
|
+
let levelEditor = new LevelEditor;
|
|
67
|
+
function setLevelEditor(editor){ levelEditor = editor; }
|
|
57
68
|
function editorMapRestore(map){ return map; }
|
|
58
69
|
function editorMapLoaded(){}
|
|
59
70
|
function editorJSONFetched(){}
|
package/src/engineSettings.js
CHANGED
|
@@ -313,6 +313,15 @@ let touchGamepadEnable = false;
|
|
|
313
313
|
* @memberof Settings */
|
|
314
314
|
let touchGamepadPassthrough = false;
|
|
315
315
|
|
|
316
|
+
/** True if a pinch of two fingers turns the mouse wheel: touchPinch is added to mouseWheel, so what zooms with
|
|
317
|
+
* the wheel zooms with a pinch, the mouse is between the two fingers, and a second finger lets go of the button
|
|
318
|
+
* the first pressed, since a pinch is not a drag; turn it off for a game with its own use for two fingers, the
|
|
319
|
+
* first finger stays the mouse and touchPinch can still be read
|
|
320
|
+
* @type {boolean}
|
|
321
|
+
* @default
|
|
322
|
+
* @memberof Settings */
|
|
323
|
+
let touchPinchWheel = true;
|
|
324
|
+
|
|
316
325
|
/** Size of center button if touch gamepad should have start button in the center
|
|
317
326
|
* - Prevents activating when pressed near virtual stick or face buttons
|
|
318
327
|
* - When the game is paused, any touch will press the button
|
|
@@ -708,6 +717,11 @@ function setTouchInputEnable(enable) { touchInputEnable = enable; }
|
|
|
708
717
|
* @memberof Settings */
|
|
709
718
|
function setTouchGamepadEnable(enable) { touchGamepadEnable = enable; }
|
|
710
719
|
|
|
720
|
+
/** Set if a pinch of two fingers turns the mouse wheel
|
|
721
|
+
* @param {boolean} enable
|
|
722
|
+
* @memberof Settings */
|
|
723
|
+
function setTouchPinchWheel(enable) { touchPinchWheel = enable; }
|
|
724
|
+
|
|
711
725
|
/** Set if touches outside the gamepad controls should still drive mouse/touch input
|
|
712
726
|
* @param {boolean} passthrough
|
|
713
727
|
* @memberof Settings */
|
package/src/engineWebGL.js
CHANGED
|
@@ -26,6 +26,19 @@ let glCanvas;
|
|
|
26
26
|
* @memberof WebGL */
|
|
27
27
|
let glContext;
|
|
28
28
|
|
|
29
|
+
/** The largest texture this device makes, in pixels across, 0 until WebGL starts; at least 2048 with WebGL2
|
|
30
|
+
* @type {number}
|
|
31
|
+
* @memberof WebGL */
|
|
32
|
+
let glMaxTextureSize = 0;
|
|
33
|
+
|
|
34
|
+
/** A texture size this device can make: the size asked for, or the largest it supports when that is smaller
|
|
35
|
+
* - The engine sizes the textures it makes itself through this, the shadow maps and the like, so a size a device
|
|
36
|
+
* does not have falls back instead of failing
|
|
37
|
+
* @param {number} size - Pixels across
|
|
38
|
+
* @return {number}
|
|
39
|
+
* @memberof WebGL */
|
|
40
|
+
function glClampTextureSize(size) { return glMaxTextureSize ? min(size, glMaxTextureSize) : size; }
|
|
41
|
+
|
|
29
42
|
/** Should WebGL be setup with anti-aliasing? must be set before calling engineInit
|
|
30
43
|
* @type {boolean}
|
|
31
44
|
* @memberof WebGL */
|
|
@@ -115,7 +128,7 @@ function glInit(rootElement)
|
|
|
115
128
|
// startup webgl, and make the textures of any texture infos made before it
|
|
116
129
|
initWebGL();
|
|
117
130
|
for (const info of glTextureInfos)
|
|
118
|
-
info.glTexture ||= glCreateTexture(info.image, info.wrap);
|
|
131
|
+
info.glTexture ||= glCreateTexture(info.image, info.wrap, info.pixelated);
|
|
119
132
|
|
|
120
133
|
// setup context lost and restore handlers
|
|
121
134
|
glCanvas.addEventListener('webglcontextlost', (e)=>
|
|
@@ -155,7 +168,7 @@ function glInit(rootElement)
|
|
|
155
168
|
glPremultipliedTextures = new WeakSet; // the tile layers draw into their new textures again below
|
|
156
169
|
initWebGL();
|
|
157
170
|
for (const info of glTextureInfos)
|
|
158
|
-
info.glTexture = glCreateTexture(info.image, info.wrap);
|
|
171
|
+
info.glTexture = glCreateTexture(info.image, info.wrap, info.pixelated);
|
|
159
172
|
pluginList.forEach(plugin=>plugin.glContextRestored?.());
|
|
160
173
|
|
|
161
174
|
// a tile layer drawn on the GPU only had its tiles in the lost texture, it draws them again
|
|
@@ -165,6 +178,8 @@ function glInit(rootElement)
|
|
|
165
178
|
|
|
166
179
|
function initWebGL()
|
|
167
180
|
{
|
|
181
|
+
glMaxTextureSize = glContext.getParameter(glContext.MAX_TEXTURE_SIZE) | 0;
|
|
182
|
+
|
|
168
183
|
// setup instanced rendering shader program
|
|
169
184
|
glShader = glCreateProgram(gl_VERTEX_SOURCE,
|
|
170
185
|
'#version 300 es\n' + // specify GLSL ES version
|
|
@@ -488,9 +503,10 @@ function glShaderProgram(shader)
|
|
|
488
503
|
* Restores the active texture when done
|
|
489
504
|
* @param {HTMLImageElement|HTMLCanvasElement|OffscreenCanvas|ImageBitmap} [image]
|
|
490
505
|
* @param {boolean|Array<number>} [wrap] - true for REPEAT, false for CLAMP_TO_EDGE, or the WebGL modes across and down
|
|
506
|
+
* @param {boolean} [pixelated] - Hard edged or smooth, undefined follows tilesPixelated
|
|
491
507
|
* @return {WebGLTexture}
|
|
492
508
|
* @memberof WebGL */
|
|
493
|
-
function glCreateTexture(image, wrap=false)
|
|
509
|
+
function glCreateTexture(image, wrap=false, pixelated=tilesPixelated)
|
|
494
510
|
{
|
|
495
511
|
if (!glContext) return;
|
|
496
512
|
|
|
@@ -499,10 +515,10 @@ function glCreateTexture(image, wrap=false)
|
|
|
499
515
|
let mipMap = false;
|
|
500
516
|
if (image?.width)
|
|
501
517
|
{
|
|
502
|
-
glSetTextureData(texture, image);
|
|
518
|
+
glSetTextureData(texture, image, pixelated);
|
|
503
519
|
glContext.bindTexture(glContext.TEXTURE_2D, texture);
|
|
504
520
|
// WebGL2 makes mipmaps at any size, a texture that becomes a render target keeps them only at powers of two
|
|
505
|
-
mipMap = !
|
|
521
|
+
mipMap = !pixelated;
|
|
506
522
|
if (mipMap && !(isPowerOfTwo(image.width) && isPowerOfTwo(image.height)))
|
|
507
523
|
glMipmapsUntilTarget.add(texture);
|
|
508
524
|
}
|
|
@@ -515,7 +531,7 @@ function glCreateTexture(image, wrap=false)
|
|
|
515
531
|
}
|
|
516
532
|
|
|
517
533
|
// set texture filtering
|
|
518
|
-
const magFilter =
|
|
534
|
+
const magFilter = pixelated ? glContext.NEAREST : glContext.LINEAR;
|
|
519
535
|
const minFilter = mipMap ? glContext.LINEAR_MIPMAP_LINEAR : magFilter;
|
|
520
536
|
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MAG_FILTER, magFilter);
|
|
521
537
|
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, minFilter);
|
|
@@ -546,8 +562,9 @@ function glDeleteTexture(texture)
|
|
|
546
562
|
/** Set WebGL texture data from an image, restores the active texture when done
|
|
547
563
|
* @param {WebGLTexture} texture
|
|
548
564
|
* @param {HTMLImageElement|HTMLCanvasElement|OffscreenCanvas|ImageBitmap} image
|
|
565
|
+
* @param {boolean} [pixelated] - Hard edged or smooth, undefined follows tilesPixelated
|
|
549
566
|
* @memberof WebGL */
|
|
550
|
-
function glSetTextureData(texture, image)
|
|
567
|
+
function glSetTextureData(texture, image, pixelated=tilesPixelated)
|
|
551
568
|
{
|
|
552
569
|
if (!glContext) return;
|
|
553
570
|
|
|
@@ -565,7 +582,7 @@ function glSetTextureData(texture, image)
|
|
|
565
582
|
}
|
|
566
583
|
// smooth filtering mixes a texel with its see through neighbors, right only for premultiplied color, or the
|
|
567
584
|
// edges go dark; pixel art is sampled a texel at a time, and uploads straight color as it always has
|
|
568
|
-
const premultiply = !
|
|
585
|
+
const premultiply = !pixelated;
|
|
569
586
|
glContext.pixelStorei(glContext.UNPACK_PREMULTIPLY_ALPHA_WEBGL, premultiply);
|
|
570
587
|
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, image);
|
|
571
588
|
glContext.pixelStorei(glContext.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
|
|
@@ -596,9 +613,9 @@ function glRegisterTextureInfo(textureInfo)
|
|
|
596
613
|
|
|
597
614
|
// create or set the texture data
|
|
598
615
|
if (textureInfo.glTexture)
|
|
599
|
-
glSetTextureData(textureInfo.glTexture, textureInfo.image);
|
|
616
|
+
glSetTextureData(textureInfo.glTexture, textureInfo.image, textureInfo.pixelated);
|
|
600
617
|
else
|
|
601
|
-
textureInfo.glTexture = glCreateTexture(textureInfo.image, textureInfo.wrap);
|
|
618
|
+
textureInfo.glTexture = glCreateTexture(textureInfo.image, textureInfo.wrap, textureInfo.pixelated);
|
|
602
619
|
}
|
|
603
620
|
|
|
604
621
|
/** Internal: tells WebGL to destroy the glTexture and stop tracking it, TextureInfo calls it
|