littlejsengine 1.24.1 → 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 +78 -1
- package/EDITOR.md +2 -0
- package/FAQ.md +13 -2
- package/README.md +1 -1
- package/REFERENCE.md +22 -7
- package/dist/littlejs.d.ts +204 -118
- package/dist/littlejs.esm.js +2479 -1983
- package/dist/littlejs.esm.min.js +3 -3
- package/dist/littlejs.js +2476 -1983
- package/dist/littlejs.min.js +2 -2
- package/dist/littlejs.release.js +2434 -1990
- package/package.json +1 -1
- package/plugins/box2d.js +9 -5
- package/plugins/gltf.js +67 -9
- package/plugins/lightSystem.js +1 -1
- package/plugins/math3d.js +1014 -1013
- package/plugins/medalSystem.js +7 -11
- package/plugins/particleEffects.js +2 -2
- package/plugins/pathFinder.js +2 -2
- package/plugins/pluginExport.js +1 -0
- package/plugins/render3d.js +131 -55
- package/plugins/render3dEditor.js +24 -10
- package/plugins/render3dExtras.js +41 -25
- package/plugins/render3dLevel.js +21 -12
- package/plugins/render3dVoxels.js +2 -2
- package/plugins/textureSheet.js +109 -7
- package/plugins/tweakables.js +1 -1
- package/plugins/tweenSystem.js +2 -2
- package/plugins/uiSystem.js +6 -2
- package/src/engine.js +60 -23
- package/src/engineAudio.js +16 -6
- package/src/engineBuild.mjs +49 -2
- package/src/engineDebug.js +4 -1
- package/src/engineDraw.js +40 -25
- package/src/engineEditor.js +82 -50
- package/src/engineExport.js +2 -0
- package/src/engineInput.js +21 -4
- package/src/engineMath.js +6 -2
- package/src/engineObject.js +675 -675
- package/src/engineParticles.js +1 -1
- package/src/engineSettings.js +16 -0
- package/src/engineTileLayer.js +85 -22
- package/src/engineUtilities.js +20 -8
- package/src/engineWebGL.js +15 -11
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
|
@@ -371,11 +371,24 @@ async function loadGLTF(url)
|
|
|
371
371
|
}
|
|
372
372
|
|
|
373
373
|
/** Parse a model from GLB bytes or glTF JSON, fetching the buffers and images it refers to
|
|
374
|
+
* - A .gltf names its .bin and image files, which are fetched from baseUrl, or found among files: what a game
|
|
375
|
+
* has in hand, like the files dropped on the page, by their paths in the drop; with files, baseUrl is the
|
|
376
|
+
* .gltf's own folder among them ('' or 'models/house/'), its names are read from there, and a name found nowhere
|
|
377
|
+
* there is taken by its file name alone only when one file of the drop has it; only an http, https or blob uri
|
|
378
|
+
* is fetched then
|
|
379
|
+
* - A file the model needs that is not found is named in the error, and an image that can not be read is named in
|
|
380
|
+
* a warning and left out
|
|
374
381
|
* @param {ArrayBuffer|Object|string} data - GLB bytes, or the glTF JSON as bytes, text or an object
|
|
375
382
|
* @param {string} [baseUrl] - Where the .bin and image files are, with its trailing slash; loadGLTF passes the file's folder
|
|
383
|
+
* @param {Map<string, Blob>} [files] - The files it refers to, by their paths, in place of fetching them
|
|
376
384
|
* @return {Promise<GLTFModel>}
|
|
385
|
+
* @example
|
|
386
|
+
* // the files of a drop, a .gltf with its .bin and textures, by their names
|
|
387
|
+
* const files = new Map([...dataTransfer.files].map((file)=> [file.name, file]));
|
|
388
|
+
* const gltf = [...files.values()].find((file)=> file.name.endsWith('.gltf'));
|
|
389
|
+
* const model = await parseGLTF(await gltf.text(), '', files);
|
|
377
390
|
* @memberof GLTF */
|
|
378
|
-
async function parseGLTF(data, baseUrl='')
|
|
391
|
+
async function parseGLTF(data, baseUrl='', files)
|
|
379
392
|
{
|
|
380
393
|
let json = data, glbBuffer;
|
|
381
394
|
if (data instanceof ArrayBuffer)
|
|
@@ -419,7 +432,7 @@ async function parseGLTF(data, baseUrl='')
|
|
|
419
432
|
ASSERT(glbBuffer && !i, 'a buffer without a uri is the GLB chunk, and only the first can be');
|
|
420
433
|
return glbBuffer;
|
|
421
434
|
}
|
|
422
|
-
return gltfFetch(buffer.uri, baseUrl).then(r=> r.arrayBuffer());
|
|
435
|
+
return gltfFetch(buffer.uri, baseUrl, files).then(r=> r.arrayBuffer());
|
|
423
436
|
}));
|
|
424
437
|
|
|
425
438
|
// the textures, decoded together first; none without WebGL, and a failed image only logs; the base color, normal
|
|
@@ -438,7 +451,7 @@ async function parseGLTF(data, baseUrl='')
|
|
|
438
451
|
const image = json.images[source], sampler = json.samplers?.[texture.sampler] || {};
|
|
439
452
|
let blob;
|
|
440
453
|
if (image.uri)
|
|
441
|
-
blob = await gltfFetch(image.uri, baseUrl).then(r=> r.blob());
|
|
454
|
+
blob = await gltfFetch(image.uri, baseUrl, files).then(r=> r.blob());
|
|
442
455
|
else
|
|
443
456
|
{
|
|
444
457
|
const view = json.bufferViews[image.bufferView];
|
|
@@ -455,14 +468,18 @@ async function parseGLTF(data, baseUrl='')
|
|
|
455
468
|
// REPEAT by default, and hard edged only when its sampler says NEAREST, not as the game's tiles are
|
|
456
469
|
return new TextureInfo(bitmap, true, [sampler.wrapS ?? 10497, sampler.wrapT ?? 10497], sampler.magFilter === 9728);
|
|
457
470
|
}
|
|
458
|
-
catch (e) {
|
|
471
|
+
catch (e) { console.warn('glTF image not loaded, left out: ' + (e?.message || e)); }
|
|
459
472
|
}));
|
|
460
473
|
|
|
461
474
|
// the parts: the scene's nodes walked with their transforms, every primitive of a node's mesh placed by it;
|
|
462
475
|
// each node's parent and resting place are kept, so an animation can move a part from where it rests
|
|
463
|
-
const parts = [], parents = [], restInverse = [], restPose = [];
|
|
476
|
+
const parts = [], parents = [], restInverse = [], restPose = [], visited = new Set;
|
|
464
477
|
const visit = (index, parentMatrix, parentIndex, parentRest)=>
|
|
465
478
|
{
|
|
479
|
+
// nodes are trees, so one reached again is a cycle or a node with two parents, a file problem
|
|
480
|
+
if (visited.has(index))
|
|
481
|
+
throw new Error('glTF node ' + index + ' is reached twice, nodes must form trees');
|
|
482
|
+
visited.add(index);
|
|
466
483
|
const node = json.nodes[index], local = gltfNodeMatrix(node);
|
|
467
484
|
let matrix = parentMatrix ? parentMatrix.copy().multiply(local) : local;
|
|
468
485
|
let rest = parentRest && parentRest.copy().multiply(local); // where it rests, when that is not where it is baked
|
|
@@ -645,8 +662,42 @@ function gltfSample(channel, time, out)
|
|
|
645
662
|
}
|
|
646
663
|
|
|
647
664
|
// fetch a uri beside the model, or decode a data uri without going out
|
|
648
|
-
function gltfFetch(uri, baseUrl)
|
|
665
|
+
function gltfFetch(uri, baseUrl, files)
|
|
649
666
|
{
|
|
667
|
+
// among the files given, by its path from the model's folder, or by its name when only one file has it; a uri
|
|
668
|
+
// of the web is still fetched, and no other kind is
|
|
669
|
+
if (files && !/^(data|https?|blob):/i.test(uri))
|
|
670
|
+
{
|
|
671
|
+
let name = uri.replace(/[?#].*$/, '');
|
|
672
|
+
try { name = decodeURIComponent(name); } catch {}
|
|
673
|
+
// a path with its . and .. steps taken, the same for the keys, which may start with ./ or / or use \
|
|
674
|
+
const clean = (text)=>
|
|
675
|
+
{
|
|
676
|
+
const parts = [];
|
|
677
|
+
for (const part of text.replace(/\\/g, '/').split('/'))
|
|
678
|
+
if (part === '..' ? !parts.pop() : part && part !== '.' && !parts.push(part))
|
|
679
|
+
return; // above the top of the files
|
|
680
|
+
return parts.join('/');
|
|
681
|
+
};
|
|
682
|
+
const path = clean(baseUrl + name);
|
|
683
|
+
if (path === undefined)
|
|
684
|
+
return Promise.reject(new Error('glTF needs ' + name + ', which is above the files given'));
|
|
685
|
+
const lower = path.toLowerCase(), file = lower.slice(lower.lastIndexOf('/') + 1);
|
|
686
|
+
const byPath = new Map([...files].map(([key, value])=> [clean(key)?.toLowerCase(), value]));
|
|
687
|
+
const found = files.get(path) ?? byPath.get(lower), keys = [...byPath.keys()];
|
|
688
|
+
if (found)
|
|
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)));
|
|
695
|
+
const named = new Set(keys.filter((k)=> k?.slice(k.lastIndexOf('/') + 1) === file).map((k)=> byPath.get(k)));
|
|
696
|
+
if (named.size === 1)
|
|
697
|
+
return Promise.resolve(new Response([...named][0]));
|
|
698
|
+
return Promise.reject(new Error('glTF needs ' + path + (named.size ? ', and more than one file has its name' :
|
|
699
|
+
', which is not among the files given')));
|
|
700
|
+
}
|
|
650
701
|
if (uri.startsWith('data:'))
|
|
651
702
|
{
|
|
652
703
|
const comma = uri.indexOf(','), bytes = atob(uri.slice(comma + 1)), data = new Uint8Array(bytes.length);
|
|
@@ -661,7 +712,7 @@ function gltfFetch(uri, baseUrl)
|
|
|
661
712
|
const url = base ? new URL(uri, base).href : baseUrl + uri;
|
|
662
713
|
return fetch(url).then(r=>
|
|
663
714
|
{
|
|
664
|
-
if (!r.ok) throw new Error('glTF
|
|
715
|
+
if (!r.ok) throw new Error('glTF needs ' + uri + ', not found at ' + url);
|
|
665
716
|
return r;
|
|
666
717
|
});
|
|
667
718
|
}
|
|
@@ -692,11 +743,18 @@ function gltfAccessor(json, buffers, index)
|
|
|
692
743
|
const size = Type.BYTES_PER_ELEMENT;
|
|
693
744
|
const scales = /** @type {Array<[Object, number]>} */ ([[Int8Array, 127], [Uint8Array, 255], [Int16Array, 32767], [Uint16Array, 65535]]);
|
|
694
745
|
const scale = a.normalized ? new Map(scales).get(Type) || 1 : 1;
|
|
746
|
+
// with a buffer under it the count is bounded by the buffer, without one it is all zeros and a sparse few,
|
|
747
|
+
// which no real model makes millions of
|
|
748
|
+
if (!view && !(a.count <= 1 << 20))
|
|
749
|
+
throw new Error('glTF accessor ' + index + ' has no buffer and a count of ' + a.count);
|
|
750
|
+
// its elements must lie in its buffer before so many floats are made for them
|
|
751
|
+
const viewEnd = view && (buffers[view.buffer]?.byteLength ?? 0), stride = view?.byteStride || components * size;
|
|
752
|
+
if (view && !((view.byteOffset || 0) + (a.byteOffset || 0) + (a.count - 1) * stride + components * size <= viewEnd))
|
|
753
|
+
throw new Error('glTF accessor ' + index + ' reaches past its buffer');
|
|
695
754
|
const out = new Float32Array(a.count * components);
|
|
696
755
|
if (view)
|
|
697
756
|
{
|
|
698
757
|
const buffer = buffers[view.buffer], offset = (view.byteOffset || 0) + (a.byteOffset || 0);
|
|
699
|
-
const stride = view.byteStride || components * size;
|
|
700
758
|
if (stride === components * size)
|
|
701
759
|
out.set(new Type(buffer, offset, a.count * components)); // packed, one view over all of it
|
|
702
760
|
else
|
|
@@ -795,7 +853,7 @@ function gltfPart(json, buffers, textures, primitive, matrix, name)
|
|
|
795
853
|
let gltfWhiteTextureInfo;
|
|
796
854
|
function gltfWhiteTexture()
|
|
797
855
|
{
|
|
798
|
-
if (gltfWhiteTextureInfo || !glContext ||
|
|
856
|
+
if (gltfWhiteTextureInfo || !glContext || !canvasAvailable()) return gltfWhiteTextureInfo;
|
|
799
857
|
const context = createCanvasContext(1);
|
|
800
858
|
context.fillStyle = '#fff';
|
|
801
859
|
context.fillRect(0, 0, 1, 1);
|
package/plugins/lightSystem.js
CHANGED
|
@@ -151,7 +151,7 @@ class LightSystemPlugin
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
// where Canvas2D draws go during the shadow and emissive passes
|
|
154
|
-
lightSystem.shadowContext ||=
|
|
154
|
+
lightSystem.shadowContext ||= createCanvasContext(1);
|
|
155
155
|
|
|
156
156
|
// resolve texture size default at init time (mainCanvasSize may
|
|
157
157
|
// not be set yet at the moment the constructor first ran), and
|