littlejsengine 1.26.0 → 1.26.1
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 +12 -0
- package/REFERENCE.md +9 -3
- package/dist/littlejs.d.ts +23 -3
- package/dist/littlejs.esm.js +46 -16
- package/dist/littlejs.esm.min.js +2 -2
- package/dist/littlejs.js +45 -16
- package/dist/littlejs.min.js +2 -2
- package/dist/littlejs.release.js +45 -16
- package/package.json +1 -1
- package/plugins/postProcess.js +5 -1
- package/plugins/render3d.js +14 -4
- package/src/engine.js +16 -5
- package/src/engineDraw.js +3 -0
- package/src/engineExport.js +1 -0
- package/src/engineInput.js +7 -6
package/dist/littlejs.js
CHANGED
|
@@ -35,7 +35,7 @@ const engineName = 'LittleJS';
|
|
|
35
35
|
* @type {string}
|
|
36
36
|
* @default
|
|
37
37
|
* @memberof Engine */
|
|
38
|
-
const engineVersion = '1.26.
|
|
38
|
+
const engineVersion = '1.26.1';
|
|
39
39
|
|
|
40
40
|
/** Frames per second to update
|
|
41
41
|
* @type {number}
|
|
@@ -69,6 +69,8 @@ let engineObjectsCollideStaticLast = [];
|
|
|
69
69
|
let frame = 0;
|
|
70
70
|
|
|
71
71
|
/** Current engine time since start in seconds
|
|
72
|
+
* - Below 20 FPS it falls behind the clock, since a frame catches up at most 50 ms of updates, so a timer in
|
|
73
|
+
* real seconds, one that must keep up on a slow device, goes by timeReal
|
|
72
74
|
* @type {number}
|
|
73
75
|
* @memberof Engine */
|
|
74
76
|
let time = 0;
|
|
@@ -94,8 +96,13 @@ function getPaused() { return paused; }
|
|
|
94
96
|
* @memberof Engine */
|
|
95
97
|
function setPaused(isPaused=true) { paused = isPaused; }
|
|
96
98
|
|
|
99
|
+
/** Frames drawn per second, smoothed over the last few seconds, in release builds too
|
|
100
|
+
* @type {number}
|
|
101
|
+
* @memberof Engine */
|
|
102
|
+
let averageFPS = 0;
|
|
103
|
+
|
|
97
104
|
// Engine internal variables
|
|
98
|
-
let frameTimeLastMS = 0, frameTimeBufferMS = 0
|
|
105
|
+
let frameTimeLastMS = 0, frameTimeBufferMS = 0;
|
|
99
106
|
|
|
100
107
|
// delta smoothing, after Time Delta Smoothing by Frank Force (2013): a frame is on screen for whole display frames,
|
|
101
108
|
// so each delta is rounded to them and the rest is carried to the next, keeping the total real time; the frame
|
|
@@ -436,7 +443,9 @@ function engineLoadingScreenDraw(elapsed)
|
|
|
436
443
|
* @param {GameCallback} [gameRender] - Called before objects are rendered, use for drawing backgrounds/world elements
|
|
437
444
|
* @param {GameCallback} [gameRenderPost] - Called after objects are rendered, use for drawing UI/overlays
|
|
438
445
|
* @param {Array<string>|string} [imageSources=[]] - List of image file paths to preload (e.g., ['player.png', 'tiles.png']), or one path
|
|
439
|
-
* @param {HTMLElement} [rootElement] - Root DOM element to attach canvas to, defaults to document.body
|
|
446
|
+
* @param {HTMLElement} [rootElement] - Root DOM element to attach canvas to, defaults to document.body; it is
|
|
447
|
+
* styled for a game (no scroll bars or selection, touch-action none),
|
|
448
|
+
* where its own inline style does not say otherwise
|
|
440
449
|
* It keeps its own inline styles and the canvas centers inside it, but the canvas is still sized from the window,
|
|
441
450
|
* so set canvasFixedSize or canvasMaxSize to fit a smaller element
|
|
442
451
|
* @example
|
|
@@ -499,8 +508,10 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
499
508
|
// by ~page-load-time when RAF starts handing real timestamps
|
|
500
509
|
if (!frameTimeLastMS) frameTimeDeltaMS = 0;
|
|
501
510
|
frameTimeLastMS = frameTimeMS;
|
|
502
|
-
|
|
503
|
-
|
|
511
|
+
// the first frame's rate seeds the average, so it does not start out climbing from 0; a gap of over a
|
|
512
|
+
// second, a hidden tab coming back, is not a frame
|
|
513
|
+
if (frameTimeDeltaMS && frameTimeDeltaMS < 1e3)
|
|
514
|
+
averageFPS = averageFPS ? lerp(averageFPS, 1e3/frameTimeDeltaMS, .05) : 1e3/frameTimeDeltaMS;
|
|
504
515
|
// the time the frame will be on screen, in whole display frames; engineStep's steps are exact already
|
|
505
516
|
if (!manualStepAtStart)
|
|
506
517
|
frameTimeDeltaMS = engineSmoothDelta(frameTimeDeltaMS);
|
|
@@ -5748,6 +5759,9 @@ class SpriteAnimator
|
|
|
5748
5759
|
* across the sprite or the mesh's own uv
|
|
5749
5760
|
* - Names in 3D only: worldPos, worldNormal, cameraPos, sunDirection, sunColor, ambientColor, ambientGroundColor,
|
|
5750
5761
|
* lightCount, lights[i], lightColors[i] and shadow()
|
|
5762
|
+
* - In 3D the snippet may also define void mainNormal(inout vec3 n), given the normal facing the camera after the
|
|
5763
|
+
* normal map, in world space, to bend it per pixel for waves or ripples; the lighting, specular, reflection and
|
|
5764
|
+
* fog then all use it, as they use a normal map
|
|
5751
5765
|
* - In 3D the shadow map is drawn without the Shader, cut only by the texture's alpha, so a snippet that removes
|
|
5752
5766
|
* parts of a surface still shadows with the whole of it
|
|
5753
5767
|
* @example
|
|
@@ -7899,8 +7913,9 @@ function inputInit()
|
|
|
7899
7913
|
}
|
|
7900
7914
|
function onContextMenu(e)
|
|
7901
7915
|
{
|
|
7902
|
-
// prevent right click menu, but a text field keeps its copy and paste menu
|
|
7903
|
-
|
|
7916
|
+
// prevent right click menu, but a text field keeps its copy and paste menu and a link its open in new tab
|
|
7917
|
+
const target = /** @type {HTMLElement} */ (e.target);
|
|
7918
|
+
if (inputPreventDefault && !isTextInput(target) && !target?.closest?.('a[href]'))
|
|
7904
7919
|
e.preventDefault();
|
|
7905
7920
|
}
|
|
7906
7921
|
function onBlur()
|
|
@@ -7921,6 +7936,10 @@ function inputInit()
|
|
|
7921
7936
|
// handle all touch events the same way
|
|
7922
7937
|
function handleTouch(e)
|
|
7923
7938
|
{
|
|
7939
|
+
// fix stalled audio requiring user interaction, with touch input off too since the page's sound needs it
|
|
7940
|
+
if (soundEnable && !headlessMode && audioContext && !audioIsRunning())
|
|
7941
|
+
audioResume();
|
|
7942
|
+
|
|
7924
7943
|
if (!touchInputEnable)
|
|
7925
7944
|
{
|
|
7926
7945
|
// turned off mid touch, the finger that drove the mouse lets go of it
|
|
@@ -7931,10 +7950,6 @@ function inputInit()
|
|
|
7931
7950
|
}
|
|
7932
7951
|
inputLastTouchTime = performance.now();
|
|
7933
7952
|
|
|
7934
|
-
// fix stalled audio requiring user interaction
|
|
7935
|
-
if (soundEnable && !headlessMode && audioContext && !audioIsRunning())
|
|
7936
|
-
audioResume();
|
|
7937
|
-
|
|
7938
7953
|
// when the touch gamepad is enabled it owns touch input: suppress the
|
|
7939
7954
|
// touch->mouse passthrough entirely unless touchGamepadPassthrough is set
|
|
7940
7955
|
// (its own zones drive gameplay via pointer events)
|
|
@@ -14193,6 +14208,10 @@ class PostProcessPlugin
|
|
|
14193
14208
|
/** @property {string} - The shadertoy style mainImage code it shades with, see setShaderCode */
|
|
14194
14209
|
this.shaderCode = shaderCode || postProcessEffects(); // no code passes the frame through
|
|
14195
14210
|
|
|
14211
|
+
/** @property {boolean} - Is the pass on? Off, the frame shows as the engine drew it, the shader is kept,
|
|
14212
|
+
* and the feedback texture holds the last frame drawn with it on */
|
|
14213
|
+
this.enabled = true;
|
|
14214
|
+
|
|
14196
14215
|
/** @property {Object<string, number|Array<number>>} - The game's own values for the shader, a uniform each
|
|
14197
14216
|
* by its name, a number a float and a list of 2 to 4 numbers a vector, set every frame as they are; an
|
|
14198
14217
|
* effect setting can be one of these names, so it changes every frame without making the shader again, all
|
|
@@ -14278,7 +14297,7 @@ class PostProcessPlugin
|
|
|
14278
14297
|
}
|
|
14279
14298
|
function postProcessRender()
|
|
14280
14299
|
{
|
|
14281
|
-
if (headlessMode || !glEnable) return;
|
|
14300
|
+
if (headlessMode || !glEnable || !postProcess.enabled) return;
|
|
14282
14301
|
|
|
14283
14302
|
// clear out the buffer, before anything here binds its own
|
|
14284
14303
|
glFlush();
|
|
@@ -24708,7 +24727,8 @@ function raycastBox(ray, pos, size, rotation)
|
|
|
24708
24727
|
* - EngineObject3D is an EngineObject with a 3D position, rotation and mesh
|
|
24709
24728
|
* - The 3D scene draws under the 2D sprites, so HUD and text land on top
|
|
24710
24729
|
* - Lighting is the sun plus ambient, with optional extra lights, fog and shadows
|
|
24711
|
-
* - Any object or draw can bring its own Shader, a mainImage snippet the lighting then applies to
|
|
24730
|
+
* - Any object or draw can bring its own Shader, a mainImage snippet the lighting then applies to, and a
|
|
24731
|
+
* mainNormal in it bends the normal the lighting uses
|
|
24712
24732
|
* - Meshes and the basic builders are in render3dMesh.js, EngineObject3D, instancing and lights in
|
|
24713
24733
|
* render3dObject.js, both right after this one; the other builders, terrain, particles, camera controls and the
|
|
24714
24734
|
* OBJ loader are in the Render3D Extras plugin, which goes after those
|
|
@@ -25286,7 +25306,9 @@ class Render3DPlugin
|
|
|
25286
25306
|
/** @property {Vector3|undefined} - Center of the shadowed area, read each frame, undefined follows the camera
|
|
25287
25307
|
* @type {Vector3|undefined} */
|
|
25288
25308
|
this.shadowCenter = undefined;
|
|
25289
|
-
/** @property {number} - Stops surfaces shadowing themselves, raise for speckles, lower if shadows drift off
|
|
25309
|
+
/** @property {number} - Stops surfaces shadowing themselves, raise for speckles, lower if shadows drift off;
|
|
25310
|
+
* a share of the shadow map's depth, twice shadowRange for the sun and a spotlight's radius, so the gap behind
|
|
25311
|
+
* a caster grows with the range as the map's texels do, and the speckles they make stay away */
|
|
25290
25312
|
this.shadowBias = .003;
|
|
25291
25313
|
/** @property {number} - How much to blur the shadow edges */
|
|
25292
25314
|
this.shadowSoftness = 1;
|
|
@@ -26320,6 +26342,9 @@ class Camera3D
|
|
|
26320
26342
|
/** @property {boolean} - Line the 3D camera up with the 2D camera, so 3D things at z=0 sit on the 2D sprites;
|
|
26321
26343
|
* it lines up with the 2D view of the main canvas, whatever canvas size a screenToRay is given */
|
|
26322
26344
|
this.align2D = false;
|
|
26345
|
+
/** @property {number} - The z of the plane align2D lines up with the 2D view, the camera sitting its
|
|
26346
|
+
* distance in front of it */
|
|
26347
|
+
this.align2DZ = 0;
|
|
26323
26348
|
}
|
|
26324
26349
|
|
|
26325
26350
|
/** Returns the camera's world transform
|
|
@@ -26390,7 +26415,7 @@ class Camera3D
|
|
|
26390
26415
|
ASSERT(!canvasHeight || distance < this.far,
|
|
26391
26416
|
'align2D needs this camera distance to match the 2D view, raise camera.far past it', distance);
|
|
26392
26417
|
this.orthographic &&= halfHeight * 2; // an orthographic camera stays orthographic and shows the same height
|
|
26393
|
-
this.pos = vec3(cameraPos.x, cameraPos.y, distance);
|
|
26418
|
+
this.pos = vec3(cameraPos.x, cameraPos.y, this.align2DZ + distance);
|
|
26394
26419
|
this.rotation = vec3(0, 0, -cameraAngle); // 2D angles turn the other way
|
|
26395
26420
|
}
|
|
26396
26421
|
}
|
|
@@ -26442,7 +26467,8 @@ const RENDER3D_SNIPPET_NAMES =
|
|
|
26442
26467
|
'#define lights extraLights\n' +
|
|
26443
26468
|
'#define lightColors extraLightColors\n';
|
|
26444
26469
|
|
|
26445
|
-
// the fragment shader; given a Shader's snippet, its mainImage replaces the texture sample and all else is the same
|
|
26470
|
+
// the fragment shader; given a Shader's snippet, its mainImage replaces the texture sample and all else is the same,
|
|
26471
|
+
// and a mainNormal in it bends the normal after the normal map, before all the lighting reads it
|
|
26446
26472
|
// uniforms: lightDir (xyz the way the sunlight travels, w = emissive, 1 or more skips the lighting),
|
|
26447
26473
|
// lightColor (the sun's rgb, a = specular), ambientFog (rgb, a = fogEnd), fogColor (rgb, a = fogStart),
|
|
26448
26474
|
// cameraPos, tex, shadowMap, shadowParams (x = shadows on, y = bias, z = blur step in texture space,
|
|
@@ -26451,6 +26477,7 @@ const RENDER3D_SNIPPET_NAMES =
|
|
|
26451
26477
|
// emissive map is on), skyTop, skyHorizon, skyBottom (what a reflection shows), normalTex, emissiveTex
|
|
26452
26478
|
function render3DFragmentSource(fragmentCode)
|
|
26453
26479
|
{
|
|
26480
|
+
const hasNormal = /\bmainNormal\s*\(/.test(fragmentCode || '');
|
|
26454
26481
|
return '#version 300 es\n' +
|
|
26455
26482
|
'precision highp float;' +
|
|
26456
26483
|
'uniform vec4 lightDir,lightColor,ambientFog,ambientGround,fogColor,shadowParams;' +
|
|
@@ -26498,6 +26525,7 @@ function render3DFragmentSource(fragmentCode)
|
|
|
26498
26525
|
'return normalize((u*m.x-v*m.y)*inversesqrt(k)+n*m.z);}' +
|
|
26499
26526
|
// a snippet's mainImage is declared here and written after main, so a define in it can not reach main
|
|
26500
26527
|
(fragmentCode ? 'void mainImage(out vec4,vec2);' : '') +
|
|
26528
|
+
(hasNormal ? 'void mainNormal(inout vec3);' : '') +
|
|
26501
26529
|
'void main(){' +
|
|
26502
26530
|
(fragmentCode ? 'vec4 t;mainImage(t,T);' : 'vec4 t=texture(tex,T);') +
|
|
26503
26531
|
'if(premultipliedTexture&&t.a>0.)t.rgb/=t.a;' + // back to straight color, what the lighting and blend expect
|
|
@@ -26507,6 +26535,7 @@ function render3DFragmentSource(fragmentCode)
|
|
|
26507
26535
|
'vec3 n=dot(N,N)>0.?normalize(N):vec3(0,1,0);' +
|
|
26508
26536
|
'if(!gl_FrontFacing)n=-n;' + // only a double sided mesh shows a back face, light it on the side that is seen
|
|
26509
26537
|
'if(materialParams.x!=0.)n=normalMapNormal(n);' +
|
|
26538
|
+
(hasNormal ? 'mainNormal(n);n=normalize(n);' : '') +
|
|
26510
26539
|
'float nl=dot(n,-lightDir.xyz);' +
|
|
26511
26540
|
// the shadow is the sun's, or one spotlight's: shadowParams.x is 1 for the sun, 2 and up for that Light3D
|
|
26512
26541
|
'float sh=shadow(),s=shadowParams.x<1.5?sh:1.;' +
|