littlejsengine 1.24.2 → 1.24.3
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/CHANGELOG.md +19 -0
- package/REFERENCE.md +2 -1
- package/dist/littlejs.d.ts +131 -109
- package/dist/littlejs.esm.js +95 -55
- package/dist/littlejs.esm.min.js +3 -3
- package/dist/littlejs.js +95 -55
- package/dist/littlejs.min.js +2 -2
- package/dist/littlejs.release.js +95 -55
- package/package.json +1 -1
- package/plugins/box2d.js +9 -5
- package/plugins/gltf.js +6 -1
- package/plugins/render3dExtras.js +12 -8
- package/plugins/textureSheet.js +15 -7
- package/src/engine.js +31 -17
- package/src/engineAudio.js +3 -3
- package/src/engineBuild.mjs +49 -2
- package/src/engineDraw.js +11 -9
- package/src/engineInput.js +6 -3
- package/src/engineWebGL.js +3 -3
package/dist/littlejs.release.js
CHANGED
|
@@ -35,7 +35,7 @@ const engineName = 'LittleJS';
|
|
|
35
35
|
* @type {string}
|
|
36
36
|
* @default
|
|
37
37
|
* @memberof Engine */
|
|
38
|
-
const engineVersion = '1.24.
|
|
38
|
+
const engineVersion = '1.24.3';
|
|
39
39
|
|
|
40
40
|
/** Frames per second to update
|
|
41
41
|
* @type {number}
|
|
@@ -168,7 +168,7 @@ let timeFixedStart = 0, frameFixedStart = 0;
|
|
|
168
168
|
let windowWidthLast = 0, windowHeightLast = 0, windowPixelRatioLast = 0;
|
|
169
169
|
let engineUpdateInternal; // assigned by engineInit so engineStep can drive it
|
|
170
170
|
let engineFrameScheduled = false; // a frame of the loop is asked for and has not run yet
|
|
171
|
-
let
|
|
171
|
+
let engineFrameErrorLast; // the last error a release build's loop went on past, as text
|
|
172
172
|
|
|
173
173
|
// the pairs of objects asked about a collision this update, so the other's own physics does not ask again: each
|
|
174
174
|
// asker's others, with true for a pair both said to resolve, and false for one left overlapping, ignored or only
|
|
@@ -422,20 +422,30 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
422
422
|
frameTimeBufferMS = 0;
|
|
423
423
|
}
|
|
424
424
|
|
|
425
|
-
// update multiple frames if necessary in case of slow framerate
|
|
426
|
-
|
|
425
|
+
// update multiple frames if necessary in case of slow framerate; a tick that throws still spends its time
|
|
426
|
+
// and the smoothing still comes back, so a release build going on past an error each tick keeps its rate
|
|
427
|
+
try
|
|
427
428
|
{
|
|
428
|
-
|
|
429
|
-
|
|
429
|
+
while (frameTimeBufferMS >= 0)
|
|
430
|
+
{
|
|
431
|
+
try
|
|
432
|
+
{
|
|
433
|
+
// read again each tick, so a pause set by the game stops the rest of this frame's catch-up ticks
|
|
434
|
+
const frozenTick = paused || !(timeScale * debugScale);
|
|
430
435
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
436
|
+
// increment frame and update time, frozen does not advance time
|
|
437
|
+
if (!frozenTick)
|
|
438
|
+
time = timeFixedStart + (frame++ - frameFixedStart) / frameRate;
|
|
439
|
+
engineTick(frozenTick);
|
|
440
|
+
}
|
|
441
|
+
finally { frameTimeBufferMS -= 1e3 / frameRate; }
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
finally
|
|
445
|
+
{
|
|
446
|
+
// add the time smoothing back in
|
|
447
|
+
frameTimeBufferMS += deltaSmooth;
|
|
435
448
|
}
|
|
436
|
-
|
|
437
|
-
// add the time smoothing back in
|
|
438
|
-
frameTimeBufferMS += deltaSmooth;
|
|
439
449
|
}
|
|
440
450
|
|
|
441
451
|
// one tick of the loop: update game and objects, when frozen update everything except them
|
|
@@ -732,9 +742,13 @@ function engineScheduleFrame()
|
|
|
732
742
|
try { engineUpdateInternal(frameTimeMS); }
|
|
733
743
|
catch (error)
|
|
734
744
|
{
|
|
735
|
-
// a release build goes on past an error in a frame, a frozen game is the worst a player can get;
|
|
736
|
-
//
|
|
737
|
-
|
|
745
|
+
// a release build goes on past an error in a frame, a frozen game is the worst a player can get; an error
|
|
746
|
+
// is logged when it is not the last one again, one every frame would flood the console
|
|
747
|
+
const text = String(error);
|
|
748
|
+
text === engineFrameErrorLast || console.error(error);
|
|
749
|
+
engineFrameErrorLast = text;
|
|
750
|
+
// the frame's input is cleared as its tick would have, or a key press that threw would be pressed again
|
|
751
|
+
inputUpdatePost();
|
|
738
752
|
engineScheduleFrame();
|
|
739
753
|
}
|
|
740
754
|
};
|
|
@@ -4311,10 +4325,13 @@ function tile(index=0, size=tileDefaultSize, texture=0, padding=tileDefaultPaddi
|
|
|
4311
4325
|
// create tile info object
|
|
4312
4326
|
const textureInfo = typeof texture === 'number' ?
|
|
4313
4327
|
textureInfos[texture] : texture;
|
|
4314
|
-
if (
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4328
|
+
if (!textureInfo?.size.x)
|
|
4329
|
+
{
|
|
4330
|
+
// no image: headless loads none and keeps the size; an image that failed to load, whose warning named it, gives
|
|
4331
|
+
// a tile of no size, which draws nothing; a slot never given an image is a mistake a debug build points out
|
|
4332
|
+
false&&ASSERT(headlessMode || textureInfo instanceof TextureInfo, 'tile texture is not loaded', texture);
|
|
4333
|
+
return new TileInfo(new Vector2, headlessMode ? size.copy() : new Vector2, textureInfo, padding, bleed);
|
|
4334
|
+
}
|
|
4318
4335
|
|
|
4319
4336
|
// get the position of the tile
|
|
4320
4337
|
const sizePaddedX = size.x + padding*2;
|
|
@@ -4782,8 +4799,8 @@ function drawTile(pos, size=vec2(1), tileInfo, color=WHITE,
|
|
|
4782
4799
|
if (headlessMode && !context) return; // headless has no canvas, only a context passed in is drawn to
|
|
4783
4800
|
|
|
4784
4801
|
const textureInfo = tileInfo?.textureInfo;
|
|
4785
|
-
if (textureInfo && !(tileInfo.size.x && tileInfo.size.y))
|
|
4786
|
-
return; // a tile with no area draws nothing, like a sprite still loading
|
|
4802
|
+
if (textureInfo && !(tileInfo.size.x && tileInfo.size.y && textureInfo.size.x))
|
|
4803
|
+
return; // a tile with no area draws nothing, like a sprite still loading, and nor does an image that failed
|
|
4787
4804
|
const bleed = tileInfo?.bleed ?? 0;
|
|
4788
4805
|
if (useWebGL && glEnable)
|
|
4789
4806
|
{
|
|
@@ -4877,7 +4894,7 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
|
4877
4894
|
* @param {boolean} [screenSpace=drawScreenSpace]
|
|
4878
4895
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
4879
4896
|
* @memberof Draw */
|
|
4880
|
-
function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=CLEAR_WHITE, angle=0, useWebGL=glEnable, screenSpace=drawScreenSpace, context)
|
|
4897
|
+
function drawRectGradient(pos, size=vec2(1), colorTop=WHITE, colorBottom=CLEAR_WHITE, angle=0, useWebGL=glEnable, screenSpace=drawScreenSpace, context)
|
|
4881
4898
|
{
|
|
4882
4899
|
false&&ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
4883
4900
|
false&&ASSERT(isVector2(size), 'size must be a vec2');
|
|
@@ -5872,8 +5889,7 @@ function bakeTintedImage(image, color, additiveColor)
|
|
|
5872
5889
|
* @param {number} dHeight
|
|
5873
5890
|
* @param {Color} color
|
|
5874
5891
|
* @param {Color} [additiveColor]
|
|
5875
|
-
* @param {number} [bleed] - How many pixels to shrink the source, used to fix bleeding
|
|
5876
|
-
* @memberof Draw */
|
|
5892
|
+
* @param {number} [bleed] - How many pixels to shrink the source, used to fix bleeding */
|
|
5877
5893
|
function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight, color, additiveColor, bleed=0)
|
|
5878
5894
|
{
|
|
5879
5895
|
const sx2 = bleed;
|
|
@@ -6699,8 +6715,9 @@ function inputInit()
|
|
|
6699
6715
|
if (soundEnable && !headlessMode && audioContext && !audioIsRunning())
|
|
6700
6716
|
audioContext.resume();
|
|
6701
6717
|
|
|
6702
|
-
// a press in the bars around a letterboxed canvas is not one on its edge; a drag out of it still moves
|
|
6703
|
-
|
|
6718
|
+
// a press in the bars around a letterboxed canvas is not one on its edge; a drag out of it still moves; under
|
|
6719
|
+
// pointer lock the mouse stays where the lock began, which may be in a bar, and every click is the game's
|
|
6720
|
+
if (!pointerLockIsActive() && !inCanvas(e.x, e.y))
|
|
6704
6721
|
return;
|
|
6705
6722
|
inputData[0][e.button] = 3;
|
|
6706
6723
|
|
|
@@ -7117,9 +7134,11 @@ function inputUpdate()
|
|
|
7117
7134
|
(gamepadIsDown(12,i)&&1) - (gamepadIsDown(13,i)&&1));
|
|
7118
7135
|
}
|
|
7119
7136
|
|
|
7120
|
-
// copy dpad to left analog stick when pressed
|
|
7137
|
+
// copy dpad to left analog stick when pressed; a gamepad with no sticks has that one, at rest when not
|
|
7121
7138
|
if (gamepadDirectionEmulateStick && (dpad.x || dpad.y))
|
|
7122
7139
|
sticks[0] = dpad.clampLength();
|
|
7140
|
+
else if (gamepadDirectionEmulateStick && !sticks.length)
|
|
7141
|
+
sticks[0] = vec2();
|
|
7123
7142
|
}
|
|
7124
7143
|
|
|
7125
7144
|
// disable touch gamepad if using real gamepad
|
|
@@ -7866,7 +7885,7 @@ class Sound
|
|
|
7866
7885
|
*/
|
|
7867
7886
|
constructor(asset, randomness, range, taper=soundDefaultTaper, onloadCallback)
|
|
7868
7887
|
{
|
|
7869
|
-
if (!soundEnable || headlessMode)
|
|
7888
|
+
if (!soundEnable || headlessMode || !audioContext) // a browser with no audio makes none either
|
|
7870
7889
|
{
|
|
7871
7890
|
// no sound is made: it counts as loaded, so a game that waits for its sounds goes on
|
|
7872
7891
|
this.loadedPercent = 1;
|
|
@@ -7936,8 +7955,8 @@ class Sound
|
|
|
7936
7955
|
// load the audio file, a URL object as bundlers give works like its string;
|
|
7937
7956
|
// report failures rather than leaving an unhandled rejection, the sound just stays unloaded and silent
|
|
7938
7957
|
const filename = asset + '';
|
|
7939
|
-
engineAddLoad(this.loadSound(filename).catch(e=>
|
|
7940
|
-
|
|
7958
|
+
engineAddLoad(this.loadSound(filename).catch(e=> // startup waits for it, and a release build says why too
|
|
7959
|
+
console.warn('Sound load failed for', filename, '-', e?.message ?? e)));
|
|
7941
7960
|
}
|
|
7942
7961
|
}
|
|
7943
7962
|
|
|
@@ -10986,7 +11005,7 @@ function glSetTextureWrap(texture, wrap=true)
|
|
|
10986
11005
|
/** Compile WebGL shader of the given type, will throw errors if in debug mode
|
|
10987
11006
|
* @param {string} source
|
|
10988
11007
|
* @param {number} type
|
|
10989
|
-
* @return {WebGLShader}
|
|
11008
|
+
* @return {WebGLShader|undefined} - undefined with no WebGL
|
|
10990
11009
|
* @memberof WebGL */
|
|
10991
11010
|
function glCompileShader(source, type)
|
|
10992
11011
|
{
|
|
@@ -11006,7 +11025,7 @@ function glCompileShader(source, type)
|
|
|
11006
11025
|
/** Create WebGL program with given shaders
|
|
11007
11026
|
* @param {string} vsSource
|
|
11008
11027
|
* @param {string} fsSource
|
|
11009
|
-
* @return {WebGLProgram}
|
|
11028
|
+
* @return {WebGLProgram|undefined} - undefined with no WebGL
|
|
11010
11029
|
* @memberof WebGL */
|
|
11011
11030
|
function glCreateProgram(vsSource, fsSource)
|
|
11012
11031
|
{
|
|
@@ -11054,7 +11073,7 @@ function glShaderProgram(shader)
|
|
|
11054
11073
|
* @param {HTMLImageElement|HTMLCanvasElement|OffscreenCanvas|ImageBitmap} [image]
|
|
11055
11074
|
* @param {boolean|Array<number>} [wrap] - true for REPEAT, false for CLAMP_TO_EDGE, or the WebGL modes across and down
|
|
11056
11075
|
* @param {boolean} [pixelated] - Hard edged or smooth, undefined follows tilesPixelated
|
|
11057
|
-
* @return {WebGLTexture}
|
|
11076
|
+
* @return {WebGLTexture|undefined} - undefined with no WebGL
|
|
11058
11077
|
* @memberof WebGL */
|
|
11059
11078
|
function glCreateTexture(image, wrap=false, pixelated=tilesPixelated)
|
|
11060
11079
|
{
|
|
@@ -16914,7 +16933,7 @@ class Box2dObject extends EngineObject
|
|
|
16914
16933
|
* @param {number} angle */
|
|
16915
16934
|
setTransform(pos, angle)
|
|
16916
16935
|
{
|
|
16917
|
-
this.pos
|
|
16936
|
+
this.pos.set(pos.x, pos.y); // its own pos kept, as an EngineObject's is
|
|
16918
16937
|
this.angle = angle;
|
|
16919
16938
|
// box2d uses reverse angle
|
|
16920
16939
|
const x = pos.x, y = pos.y;
|
|
@@ -16930,7 +16949,7 @@ class Box2dObject extends EngineObject
|
|
|
16930
16949
|
* @param {Vector2} pos */
|
|
16931
16950
|
setPosition(pos)
|
|
16932
16951
|
{
|
|
16933
|
-
this.pos
|
|
16952
|
+
this.pos.set(pos.x, pos.y); // its own pos kept, as an EngineObject's is
|
|
16934
16953
|
const x = pos.x, y = pos.y;
|
|
16935
16954
|
box2dWhenUnlocked(()=>
|
|
16936
16955
|
{
|
|
@@ -18431,7 +18450,8 @@ class Box2dPlugin
|
|
|
18431
18450
|
return box2d.raycastAll(start, end, includeSensors)[0];
|
|
18432
18451
|
}
|
|
18433
18452
|
|
|
18434
|
-
/** box
|
|
18453
|
+
/** Every object whose shapes' bounding boxes overlap a box, not the shapes themselves, so near the corner of a
|
|
18454
|
+
* turned box or a circle it finds one the box does not touch; use pointCast or a raycast for exact
|
|
18435
18455
|
* @param {Vector2} pos
|
|
18436
18456
|
* @param {Vector2} size
|
|
18437
18457
|
* @param {boolean} [includeSensors] - Also find sensors, trigger zones are passed through by default
|
|
@@ -18461,7 +18481,8 @@ class Box2dPlugin
|
|
|
18461
18481
|
return queryObjects;
|
|
18462
18482
|
}
|
|
18463
18483
|
|
|
18464
|
-
/** box
|
|
18484
|
+
/** The first object whose shapes' bounding boxes overlap a box, not the shapes themselves, so near the corner of
|
|
18485
|
+
* a turned box or a circle it finds one the box does not touch; use pointCast or a raycast for exact
|
|
18465
18486
|
* @param {Vector2} pos
|
|
18466
18487
|
* @param {Vector2} size
|
|
18467
18488
|
* @param {boolean} [includeSensors] - Also find sensors, trigger zones are passed through by default
|
|
@@ -18721,8 +18742,10 @@ async function box2dInit()
|
|
|
18721
18742
|
{
|
|
18722
18743
|
if (o.body)
|
|
18723
18744
|
{
|
|
18745
|
+
// moved in place, as the engine moves an EngineObject's pos, so what holds it follows the body;
|
|
18724
18746
|
// box2d uses reverse angle
|
|
18725
|
-
|
|
18747
|
+
const p = o.body.GetPosition();
|
|
18748
|
+
o.pos.set(p.get_x(), p.get_y());
|
|
18726
18749
|
o.angle = -o.body.GetAngle();
|
|
18727
18750
|
}
|
|
18728
18751
|
}
|
|
@@ -19218,6 +19241,8 @@ let textureSheetPendingCount = 0;
|
|
|
19218
19241
|
const textureSheetLoaded = new Map;
|
|
19219
19242
|
// a load's key: what it was given, a vector by its numbers
|
|
19220
19243
|
const textureSheetLoadKey = (...parts)=> parts.map((p)=> isVector2(p) ? p.x + ',' + p.y : String(p)).join('|');
|
|
19244
|
+
// a load whose file failed is forgotten, so loading it again tries again, as when the server comes back
|
|
19245
|
+
const textureSheetLoadFailed = (key, loaded)=> textureSheetLoaded.get(key) === loaded && textureSheetLoaded.delete(key);
|
|
19221
19246
|
|
|
19222
19247
|
/**
|
|
19223
19248
|
* Texture Sheet - A texture that images are packed into as they load
|
|
@@ -19374,7 +19399,8 @@ class TextureSheet
|
|
|
19374
19399
|
* - Pass frameSize for animations, then step through them with TileInfo.frame
|
|
19375
19400
|
* - Grid images keep their layout and frames wrap down to the next row
|
|
19376
19401
|
* - Pass sourcePadding if the source image has padding baked in around frames
|
|
19377
|
-
* - The same image loaded again with the same settings gives back what the first load did, packed once
|
|
19402
|
+
* - The same image loaded again with the same settings gives back what the first load did, packed once, unless
|
|
19403
|
+
* that load failed
|
|
19378
19404
|
* @param {string} src - Image source path
|
|
19379
19405
|
* @param {Vector2|number} [frameSize] - Size of each animation frame in pixels, or the whole image less its
|
|
19380
19406
|
* source padding if not passed
|
|
@@ -19429,7 +19455,7 @@ function loadSprite(src, frameSize, padding=textureSheetPadding, sourcePadding=0
|
|
|
19429
19455
|
if (!added)
|
|
19430
19456
|
{
|
|
19431
19457
|
// leave the tile empty, no sheet can hold it
|
|
19432
|
-
|
|
19458
|
+
console.warn('loadSprite image is too large to fit on a texture sheet:', src);
|
|
19433
19459
|
return;
|
|
19434
19460
|
}
|
|
19435
19461
|
Object.assign(tileInfo, added.tile);
|
|
@@ -19438,7 +19464,8 @@ function loadSprite(src, frameSize, padding=textureSheetPadding, sourcePadding=0
|
|
|
19438
19464
|
else
|
|
19439
19465
|
{
|
|
19440
19466
|
// leave the tile empty if the image failed to load
|
|
19441
|
-
|
|
19467
|
+
console.warn('loadSprite failed to load image:', src);
|
|
19468
|
+
textureSheetLoadFailed(key, tileInfo);
|
|
19442
19469
|
}
|
|
19443
19470
|
});
|
|
19444
19471
|
|
|
@@ -19456,7 +19483,8 @@ function loadSprite(src, frameSize, padding=textureSheetPadding, sourcePadding=0
|
|
|
19456
19483
|
* is not read, cut it into its tiles first
|
|
19457
19484
|
* - An image that fails to load, or that no sheet can hold, adds no tiles and says so in the console, so the tiles
|
|
19458
19485
|
* of the images after it move up
|
|
19459
|
-
* - The same images loaded again with the same settings give back what the first load did, packed once
|
|
19486
|
+
* - The same images loaded again with the same settings give back what the first load did, packed once, unless
|
|
19487
|
+
* an image of it failed
|
|
19460
19488
|
* @param {Array<string>} sources - Image source paths
|
|
19461
19489
|
* @param {Vector2|number} [tileSize] - Size of a tile in pixels
|
|
19462
19490
|
* @param {number} [padding] - How many pixels padding around each tile on the sheet
|
|
@@ -19498,6 +19526,7 @@ function loadTiles(sources, tileSize=tileDefaultSize, padding=textureSheetPaddin
|
|
|
19498
19526
|
// the whole tiles of it, an edge past the last one left out
|
|
19499
19527
|
const columns = image.width / size.x | 0, rows = image.height / size.y | 0, count = columns * rows;
|
|
19500
19528
|
const added = count && textureSheetAdd(vec2(columns * size.x, rows * size.y), size, padding, 0);
|
|
19529
|
+
image.width || textureSheetLoadFailed(key, set);
|
|
19501
19530
|
if (!added)
|
|
19502
19531
|
return console.warn('loadTiles: ' + src + (count ? ' does not fit on a texture sheet' :
|
|
19503
19532
|
' failed to load, or is smaller than a tile') + ', its tiles are left out');
|
|
@@ -19516,7 +19545,8 @@ function loadTiles(sources, tileSize=tileDefaultSize, padding=textureSheetPaddin
|
|
|
19516
19545
|
* - Aseprite frame tags become animations, so do names like run_0, run_1
|
|
19517
19546
|
* - Trimmed frames are restored to their full source size when packed
|
|
19518
19547
|
* - Rotated frames are rotated back upright when packed
|
|
19519
|
-
* - The same atlas loaded again by its paths, with the same padding, gives back what the first load did, packed
|
|
19548
|
+
* - The same atlas loaded again by its paths, with the same padding, gives back what the first load did, packed
|
|
19549
|
+
* once, unless that load failed
|
|
19520
19550
|
* @param {string} imageSrc - Atlas image path
|
|
19521
19551
|
* @param {string|Object} jsonSrc - Atlas json path, or already parsed json data
|
|
19522
19552
|
* @param {number} [padding] - How many pixels padding around each frame
|
|
@@ -19571,7 +19601,7 @@ function loadAtlas(imageSrc, jsonSrc, padding=textureSheetPadding)
|
|
|
19571
19601
|
const added = textureSheetAdd(blockSize, sourceSize, padding);
|
|
19572
19602
|
if (!added)
|
|
19573
19603
|
{
|
|
19574
|
-
|
|
19604
|
+
console.warn('loadAtlas frames are too large to fit on a texture sheet:', group.name);
|
|
19575
19605
|
continue;
|
|
19576
19606
|
}
|
|
19577
19607
|
const {sheet, tile} = added;
|
|
@@ -19605,7 +19635,8 @@ function loadAtlas(imageSrc, jsonSrc, padding=textureSheetPadding)
|
|
|
19605
19635
|
else
|
|
19606
19636
|
{
|
|
19607
19637
|
// leave the atlas empty if either file failed to load
|
|
19608
|
-
|
|
19638
|
+
console.warn('loadAtlas failed to load:', imageSrc, jsonSrc);
|
|
19639
|
+
textureSheetLoadFailed(key, atlas);
|
|
19609
19640
|
}
|
|
19610
19641
|
});
|
|
19611
19642
|
|
|
@@ -28048,9 +28079,9 @@ class HeightMap extends EngineObject3D
|
|
|
28048
28079
|
levelSegment3D(from, to)
|
|
28049
28080
|
{
|
|
28050
28081
|
const m = this.pos3D, size = this.mapSize;
|
|
28051
|
-
if (abs(to.x - m.x) > size.x / 2 || abs(to.z - m.z) > size.y / 2) return; // off the map
|
|
28082
|
+
if (abs(to.x - m.x) > size.x / 2 || abs(to.z - m.z) > size.y / 2) return undefined; // off the map
|
|
28052
28083
|
const a = from.y - this.getHeight(from.x, from.z), b = to.y - this.getHeight(to.x, to.z);
|
|
28053
|
-
if (a < 0 || b >= 0) return; // above all the way, or under from the start
|
|
28084
|
+
if (a < 0 || b >= 0) return undefined; // above all the way, or under from the start
|
|
28054
28085
|
const distance = a / (a - b);
|
|
28055
28086
|
return {distance, normal: this.getNormal(from.x + (to.x - from.x) * distance, from.z + (to.z - from.z) * distance)};
|
|
28056
28087
|
}
|
|
@@ -29088,9 +29119,11 @@ class LensFlare3D extends EngineObject3D
|
|
|
29088
29119
|
return this.made;
|
|
29089
29120
|
}
|
|
29090
29121
|
|
|
29091
|
-
|
|
29092
|
-
|
|
29093
|
-
|
|
29122
|
+
/** What the flare is of, seen from the camera: the way to it, how far it is, Infinity for the sun and for a
|
|
29123
|
+
* directional light, which shines from its place toward the origin, and a point to find it on the screen by;
|
|
29124
|
+
* undefined with no direction, or a light that is gone or at the camera
|
|
29125
|
+
* @return {{direction: Vector3, distance: number, pos: Vector3}|undefined}
|
|
29126
|
+
* @ignore */
|
|
29094
29127
|
flareSource()
|
|
29095
29128
|
{
|
|
29096
29129
|
const camera = render3D.camera.pos, light = this.light;
|
|
@@ -29106,9 +29139,11 @@ class LensFlare3D extends EngineObject3D
|
|
|
29106
29139
|
return distance ? {direction: offset.scale(1 / distance), distance, pos} : undefined;
|
|
29107
29140
|
}
|
|
29108
29141
|
|
|
29109
|
-
|
|
29110
|
-
|
|
29111
|
-
|
|
29142
|
+
/** How the flare would show with nothing in the way: where its source is on the screen, how strong it is there,
|
|
29143
|
+
* fading as it leaves the screen, its tint and the height its sizes are parts of; undefined when it would not
|
|
29144
|
+
* show at all, behind the camera, off the screen, or of a light that is off or seen from outside its cone
|
|
29145
|
+
* @return {{sun: Vector2, center: Vector2, strength: number, tint: Color, height: number}|undefined}
|
|
29146
|
+
* @ignore */
|
|
29112
29147
|
flareLook()
|
|
29113
29148
|
{
|
|
29114
29149
|
const source = this.flareSource(), center = mainCanvasSize.scale(.5);
|
|
@@ -32409,11 +32444,16 @@ function gltfFetch(uri, baseUrl, files)
|
|
|
32409
32444
|
const path = clean(baseUrl + name);
|
|
32410
32445
|
if (path === undefined)
|
|
32411
32446
|
return Promise.reject(new Error('glTF needs ' + name + ', which is above the files given'));
|
|
32412
|
-
const lower = path.toLowerCase(), file =
|
|
32447
|
+
const lower = path.toLowerCase(), file = lower.slice(lower.lastIndexOf('/') + 1);
|
|
32413
32448
|
const byPath = new Map([...files].map(([key, value])=> [clean(key)?.toLowerCase(), value]));
|
|
32414
32449
|
const found = files.get(path) ?? byPath.get(lower), keys = [...byPath.keys()];
|
|
32415
32450
|
if (found)
|
|
32416
32451
|
return Promise.resolve(new Response(found));
|
|
32452
|
+
// what is in a folder dropped, not the folder, leaves the folder's own name out of the paths: the longest key
|
|
32453
|
+
// the path ends with, a folder and more, as a name alone is only taken when one file has it
|
|
32454
|
+
const end = keys.filter((k)=> k?.includes('/') && lower.endsWith('/' + k)).sort((a, b)=> b.length - a.length)[0];
|
|
32455
|
+
if (end)
|
|
32456
|
+
return Promise.resolve(new Response(byPath.get(end)));
|
|
32417
32457
|
const named = new Set(keys.filter((k)=> k?.slice(k.lastIndexOf('/') + 1) === file).map((k)=> byPath.get(k)));
|
|
32418
32458
|
if (named.size === 1)
|
|
32419
32459
|
return Promise.resolve(new Response([...named][0]));
|
package/package.json
CHANGED
package/plugins/box2d.js
CHANGED
|
@@ -627,7 +627,7 @@ class Box2dObject extends EngineObject
|
|
|
627
627
|
* @param {number} angle */
|
|
628
628
|
setTransform(pos, angle)
|
|
629
629
|
{
|
|
630
|
-
this.pos
|
|
630
|
+
this.pos.set(pos.x, pos.y); // its own pos kept, as an EngineObject's is
|
|
631
631
|
this.angle = angle;
|
|
632
632
|
// box2d uses reverse angle
|
|
633
633
|
const x = pos.x, y = pos.y;
|
|
@@ -643,7 +643,7 @@ class Box2dObject extends EngineObject
|
|
|
643
643
|
* @param {Vector2} pos */
|
|
644
644
|
setPosition(pos)
|
|
645
645
|
{
|
|
646
|
-
this.pos
|
|
646
|
+
this.pos.set(pos.x, pos.y); // its own pos kept, as an EngineObject's is
|
|
647
647
|
const x = pos.x, y = pos.y;
|
|
648
648
|
box2dWhenUnlocked(()=>
|
|
649
649
|
{
|
|
@@ -2144,7 +2144,8 @@ class Box2dPlugin
|
|
|
2144
2144
|
return box2d.raycastAll(start, end, includeSensors)[0];
|
|
2145
2145
|
}
|
|
2146
2146
|
|
|
2147
|
-
/** box
|
|
2147
|
+
/** Every object whose shapes' bounding boxes overlap a box, not the shapes themselves, so near the corner of a
|
|
2148
|
+
* turned box or a circle it finds one the box does not touch; use pointCast or a raycast for exact
|
|
2148
2149
|
* @param {Vector2} pos
|
|
2149
2150
|
* @param {Vector2} size
|
|
2150
2151
|
* @param {boolean} [includeSensors] - Also find sensors, trigger zones are passed through by default
|
|
@@ -2174,7 +2175,8 @@ class Box2dPlugin
|
|
|
2174
2175
|
return queryObjects;
|
|
2175
2176
|
}
|
|
2176
2177
|
|
|
2177
|
-
/** box
|
|
2178
|
+
/** The first object whose shapes' bounding boxes overlap a box, not the shapes themselves, so near the corner of
|
|
2179
|
+
* a turned box or a circle it finds one the box does not touch; use pointCast or a raycast for exact
|
|
2178
2180
|
* @param {Vector2} pos
|
|
2179
2181
|
* @param {Vector2} size
|
|
2180
2182
|
* @param {boolean} [includeSensors] - Also find sensors, trigger zones are passed through by default
|
|
@@ -2434,8 +2436,10 @@ async function box2dInit()
|
|
|
2434
2436
|
{
|
|
2435
2437
|
if (o.body)
|
|
2436
2438
|
{
|
|
2439
|
+
// moved in place, as the engine moves an EngineObject's pos, so what holds it follows the body;
|
|
2437
2440
|
// box2d uses reverse angle
|
|
2438
|
-
|
|
2441
|
+
const p = o.body.GetPosition();
|
|
2442
|
+
o.pos.set(p.get_x(), p.get_y());
|
|
2439
2443
|
o.angle = -o.body.GetAngle();
|
|
2440
2444
|
}
|
|
2441
2445
|
}
|
package/plugins/gltf.js
CHANGED
|
@@ -682,11 +682,16 @@ function gltfFetch(uri, baseUrl, files)
|
|
|
682
682
|
const path = clean(baseUrl + name);
|
|
683
683
|
if (path === undefined)
|
|
684
684
|
return Promise.reject(new Error('glTF needs ' + name + ', which is above the files given'));
|
|
685
|
-
const lower = path.toLowerCase(), file =
|
|
685
|
+
const lower = path.toLowerCase(), file = lower.slice(lower.lastIndexOf('/') + 1);
|
|
686
686
|
const byPath = new Map([...files].map(([key, value])=> [clean(key)?.toLowerCase(), value]));
|
|
687
687
|
const found = files.get(path) ?? byPath.get(lower), keys = [...byPath.keys()];
|
|
688
688
|
if (found)
|
|
689
689
|
return Promise.resolve(new Response(found));
|
|
690
|
+
// what is in a folder dropped, not the folder, leaves the folder's own name out of the paths: the longest key
|
|
691
|
+
// the path ends with, a folder and more, as a name alone is only taken when one file has it
|
|
692
|
+
const end = keys.filter((k)=> k?.includes('/') && lower.endsWith('/' + k)).sort((a, b)=> b.length - a.length)[0];
|
|
693
|
+
if (end)
|
|
694
|
+
return Promise.resolve(new Response(byPath.get(end)));
|
|
690
695
|
const named = new Set(keys.filter((k)=> k?.slice(k.lastIndexOf('/') + 1) === file).map((k)=> byPath.get(k)));
|
|
691
696
|
if (named.size === 1)
|
|
692
697
|
return Promise.resolve(new Response([...named][0]));
|
|
@@ -631,9 +631,9 @@ class HeightMap extends EngineObject3D
|
|
|
631
631
|
levelSegment3D(from, to)
|
|
632
632
|
{
|
|
633
633
|
const m = this.pos3D, size = this.mapSize;
|
|
634
|
-
if (abs(to.x - m.x) > size.x / 2 || abs(to.z - m.z) > size.y / 2) return; // off the map
|
|
634
|
+
if (abs(to.x - m.x) > size.x / 2 || abs(to.z - m.z) > size.y / 2) return undefined; // off the map
|
|
635
635
|
const a = from.y - this.getHeight(from.x, from.z), b = to.y - this.getHeight(to.x, to.z);
|
|
636
|
-
if (a < 0 || b >= 0) return; // above all the way, or under from the start
|
|
636
|
+
if (a < 0 || b >= 0) return undefined; // above all the way, or under from the start
|
|
637
637
|
const distance = a / (a - b);
|
|
638
638
|
return {distance, normal: this.getNormal(from.x + (to.x - from.x) * distance, from.z + (to.z - from.z) * distance)};
|
|
639
639
|
}
|
|
@@ -1671,9 +1671,11 @@ class LensFlare3D extends EngineObject3D
|
|
|
1671
1671
|
return this.made;
|
|
1672
1672
|
}
|
|
1673
1673
|
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1674
|
+
/** What the flare is of, seen from the camera: the way to it, how far it is, Infinity for the sun and for a
|
|
1675
|
+
* directional light, which shines from its place toward the origin, and a point to find it on the screen by;
|
|
1676
|
+
* undefined with no direction, or a light that is gone or at the camera
|
|
1677
|
+
* @return {{direction: Vector3, distance: number, pos: Vector3}|undefined}
|
|
1678
|
+
* @ignore */
|
|
1677
1679
|
flareSource()
|
|
1678
1680
|
{
|
|
1679
1681
|
const camera = render3D.camera.pos, light = this.light;
|
|
@@ -1689,9 +1691,11 @@ class LensFlare3D extends EngineObject3D
|
|
|
1689
1691
|
return distance ? {direction: offset.scale(1 / distance), distance, pos} : undefined;
|
|
1690
1692
|
}
|
|
1691
1693
|
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1694
|
+
/** How the flare would show with nothing in the way: where its source is on the screen, how strong it is there,
|
|
1695
|
+
* fading as it leaves the screen, its tint and the height its sizes are parts of; undefined when it would not
|
|
1696
|
+
* show at all, behind the camera, off the screen, or of a light that is off or seen from outside its cone
|
|
1697
|
+
* @return {{sun: Vector2, center: Vector2, strength: number, tint: Color, height: number}|undefined}
|
|
1698
|
+
* @ignore */
|
|
1695
1699
|
flareLook()
|
|
1696
1700
|
{
|
|
1697
1701
|
const source = this.flareSource(), center = mainCanvasSize.scale(.5);
|
package/plugins/textureSheet.js
CHANGED
|
@@ -37,6 +37,8 @@ let textureSheetPendingCount = 0;
|
|
|
37
37
|
const textureSheetLoaded = new Map;
|
|
38
38
|
// a load's key: what it was given, a vector by its numbers
|
|
39
39
|
const textureSheetLoadKey = (...parts)=> parts.map((p)=> isVector2(p) ? p.x + ',' + p.y : String(p)).join('|');
|
|
40
|
+
// a load whose file failed is forgotten, so loading it again tries again, as when the server comes back
|
|
41
|
+
const textureSheetLoadFailed = (key, loaded)=> textureSheetLoaded.get(key) === loaded && textureSheetLoaded.delete(key);
|
|
40
42
|
|
|
41
43
|
/**
|
|
42
44
|
* Texture Sheet - A texture that images are packed into as they load
|
|
@@ -193,7 +195,8 @@ class TextureSheet
|
|
|
193
195
|
* - Pass frameSize for animations, then step through them with TileInfo.frame
|
|
194
196
|
* - Grid images keep their layout and frames wrap down to the next row
|
|
195
197
|
* - Pass sourcePadding if the source image has padding baked in around frames
|
|
196
|
-
* - The same image loaded again with the same settings gives back what the first load did, packed once
|
|
198
|
+
* - The same image loaded again with the same settings gives back what the first load did, packed once, unless
|
|
199
|
+
* that load failed
|
|
197
200
|
* @param {string} src - Image source path
|
|
198
201
|
* @param {Vector2|number} [frameSize] - Size of each animation frame in pixels, or the whole image less its
|
|
199
202
|
* source padding if not passed
|
|
@@ -248,7 +251,7 @@ function loadSprite(src, frameSize, padding=textureSheetPadding, sourcePadding=0
|
|
|
248
251
|
if (!added)
|
|
249
252
|
{
|
|
250
253
|
// leave the tile empty, no sheet can hold it
|
|
251
|
-
|
|
254
|
+
console.warn('loadSprite image is too large to fit on a texture sheet:', src);
|
|
252
255
|
return;
|
|
253
256
|
}
|
|
254
257
|
Object.assign(tileInfo, added.tile);
|
|
@@ -257,7 +260,8 @@ function loadSprite(src, frameSize, padding=textureSheetPadding, sourcePadding=0
|
|
|
257
260
|
else
|
|
258
261
|
{
|
|
259
262
|
// leave the tile empty if the image failed to load
|
|
260
|
-
|
|
263
|
+
console.warn('loadSprite failed to load image:', src);
|
|
264
|
+
textureSheetLoadFailed(key, tileInfo);
|
|
261
265
|
}
|
|
262
266
|
});
|
|
263
267
|
|
|
@@ -275,7 +279,8 @@ function loadSprite(src, frameSize, padding=textureSheetPadding, sourcePadding=0
|
|
|
275
279
|
* is not read, cut it into its tiles first
|
|
276
280
|
* - An image that fails to load, or that no sheet can hold, adds no tiles and says so in the console, so the tiles
|
|
277
281
|
* of the images after it move up
|
|
278
|
-
* - The same images loaded again with the same settings give back what the first load did, packed once
|
|
282
|
+
* - The same images loaded again with the same settings give back what the first load did, packed once, unless
|
|
283
|
+
* an image of it failed
|
|
279
284
|
* @param {Array<string>} sources - Image source paths
|
|
280
285
|
* @param {Vector2|number} [tileSize] - Size of a tile in pixels
|
|
281
286
|
* @param {number} [padding] - How many pixels padding around each tile on the sheet
|
|
@@ -317,6 +322,7 @@ function loadTiles(sources, tileSize=tileDefaultSize, padding=textureSheetPaddin
|
|
|
317
322
|
// the whole tiles of it, an edge past the last one left out
|
|
318
323
|
const columns = image.width / size.x | 0, rows = image.height / size.y | 0, count = columns * rows;
|
|
319
324
|
const added = count && textureSheetAdd(vec2(columns * size.x, rows * size.y), size, padding, 0);
|
|
325
|
+
image.width || textureSheetLoadFailed(key, set);
|
|
320
326
|
if (!added)
|
|
321
327
|
return console.warn('loadTiles: ' + src + (count ? ' does not fit on a texture sheet' :
|
|
322
328
|
' failed to load, or is smaller than a tile') + ', its tiles are left out');
|
|
@@ -335,7 +341,8 @@ function loadTiles(sources, tileSize=tileDefaultSize, padding=textureSheetPaddin
|
|
|
335
341
|
* - Aseprite frame tags become animations, so do names like run_0, run_1
|
|
336
342
|
* - Trimmed frames are restored to their full source size when packed
|
|
337
343
|
* - Rotated frames are rotated back upright when packed
|
|
338
|
-
* - The same atlas loaded again by its paths, with the same padding, gives back what the first load did, packed
|
|
344
|
+
* - The same atlas loaded again by its paths, with the same padding, gives back what the first load did, packed
|
|
345
|
+
* once, unless that load failed
|
|
339
346
|
* @param {string} imageSrc - Atlas image path
|
|
340
347
|
* @param {string|Object} jsonSrc - Atlas json path, or already parsed json data
|
|
341
348
|
* @param {number} [padding] - How many pixels padding around each frame
|
|
@@ -390,7 +397,7 @@ function loadAtlas(imageSrc, jsonSrc, padding=textureSheetPadding)
|
|
|
390
397
|
const added = textureSheetAdd(blockSize, sourceSize, padding);
|
|
391
398
|
if (!added)
|
|
392
399
|
{
|
|
393
|
-
|
|
400
|
+
console.warn('loadAtlas frames are too large to fit on a texture sheet:', group.name);
|
|
394
401
|
continue;
|
|
395
402
|
}
|
|
396
403
|
const {sheet, tile} = added;
|
|
@@ -424,7 +431,8 @@ function loadAtlas(imageSrc, jsonSrc, padding=textureSheetPadding)
|
|
|
424
431
|
else
|
|
425
432
|
{
|
|
426
433
|
// leave the atlas empty if either file failed to load
|
|
427
|
-
|
|
434
|
+
console.warn('loadAtlas failed to load:', imageSrc, jsonSrc);
|
|
435
|
+
textureSheetLoadFailed(key, atlas);
|
|
428
436
|
}
|
|
429
437
|
});
|
|
430
438
|
|