littlejsengine 1.24.1 → 1.24.2
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 +59 -1
- package/EDITOR.md +2 -0
- package/FAQ.md +13 -2
- package/README.md +1 -1
- package/REFERENCE.md +20 -6
- package/dist/littlejs.d.ts +77 -13
- package/dist/littlejs.esm.js +2395 -1939
- package/dist/littlejs.esm.min.js +3 -3
- package/dist/littlejs.js +2392 -1939
- package/dist/littlejs.min.js +2 -2
- package/dist/littlejs.release.js +2356 -1952
- package/package.json +1 -1
- package/plugins/gltf.js +62 -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 +29 -17
- package/plugins/render3dLevel.js +21 -12
- package/plugins/render3dVoxels.js +2 -2
- package/plugins/textureSheet.js +97 -3
- package/plugins/tweakables.js +1 -1
- package/plugins/tweenSystem.js +2 -2
- package/plugins/uiSystem.js +6 -2
- package/src/engine.js +34 -11
- package/src/engineAudio.js +13 -3
- package/src/engineDebug.js +4 -1
- package/src/engineDraw.js +29 -16
- package/src/engineEditor.js +82 -50
- package/src/engineExport.js +2 -0
- package/src/engineInput.js +17 -3
- 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 +12 -8
package/package.json
CHANGED
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,37 @@ 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 = path.slice(path.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
|
+
const named = new Set(keys.filter((k)=> k?.slice(k.lastIndexOf('/') + 1) === file).map((k)=> byPath.get(k)));
|
|
691
|
+
if (named.size === 1)
|
|
692
|
+
return Promise.resolve(new Response([...named][0]));
|
|
693
|
+
return Promise.reject(new Error('glTF needs ' + path + (named.size ? ', and more than one file has its name' :
|
|
694
|
+
', which is not among the files given')));
|
|
695
|
+
}
|
|
650
696
|
if (uri.startsWith('data:'))
|
|
651
697
|
{
|
|
652
698
|
const comma = uri.indexOf(','), bytes = atob(uri.slice(comma + 1)), data = new Uint8Array(bytes.length);
|
|
@@ -661,7 +707,7 @@ function gltfFetch(uri, baseUrl)
|
|
|
661
707
|
const url = base ? new URL(uri, base).href : baseUrl + uri;
|
|
662
708
|
return fetch(url).then(r=>
|
|
663
709
|
{
|
|
664
|
-
if (!r.ok) throw new Error('glTF
|
|
710
|
+
if (!r.ok) throw new Error('glTF needs ' + uri + ', not found at ' + url);
|
|
665
711
|
return r;
|
|
666
712
|
});
|
|
667
713
|
}
|
|
@@ -692,11 +738,18 @@ function gltfAccessor(json, buffers, index)
|
|
|
692
738
|
const size = Type.BYTES_PER_ELEMENT;
|
|
693
739
|
const scales = /** @type {Array<[Object, number]>} */ ([[Int8Array, 127], [Uint8Array, 255], [Int16Array, 32767], [Uint16Array, 65535]]);
|
|
694
740
|
const scale = a.normalized ? new Map(scales).get(Type) || 1 : 1;
|
|
741
|
+
// with a buffer under it the count is bounded by the buffer, without one it is all zeros and a sparse few,
|
|
742
|
+
// which no real model makes millions of
|
|
743
|
+
if (!view && !(a.count <= 1 << 20))
|
|
744
|
+
throw new Error('glTF accessor ' + index + ' has no buffer and a count of ' + a.count);
|
|
745
|
+
// its elements must lie in its buffer before so many floats are made for them
|
|
746
|
+
const viewEnd = view && (buffers[view.buffer]?.byteLength ?? 0), stride = view?.byteStride || components * size;
|
|
747
|
+
if (view && !((view.byteOffset || 0) + (a.byteOffset || 0) + (a.count - 1) * stride + components * size <= viewEnd))
|
|
748
|
+
throw new Error('glTF accessor ' + index + ' reaches past its buffer');
|
|
695
749
|
const out = new Float32Array(a.count * components);
|
|
696
750
|
if (view)
|
|
697
751
|
{
|
|
698
752
|
const buffer = buffers[view.buffer], offset = (view.byteOffset || 0) + (a.byteOffset || 0);
|
|
699
|
-
const stride = view.byteStride || components * size;
|
|
700
753
|
if (stride === components * size)
|
|
701
754
|
out.set(new Type(buffer, offset, a.count * components)); // packed, one view over all of it
|
|
702
755
|
else
|
|
@@ -795,7 +848,7 @@ function gltfPart(json, buffers, textures, primitive, matrix, name)
|
|
|
795
848
|
let gltfWhiteTextureInfo;
|
|
796
849
|
function gltfWhiteTexture()
|
|
797
850
|
{
|
|
798
|
-
if (gltfWhiteTextureInfo || !glContext ||
|
|
851
|
+
if (gltfWhiteTextureInfo || !glContext || !canvasAvailable()) return gltfWhiteTextureInfo;
|
|
799
852
|
const context = createCanvasContext(1);
|
|
800
853
|
context.fillStyle = '#fff';
|
|
801
854
|
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
|