littlejsengine 1.24.3 → 1.26.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.
Files changed (53) hide show
  1. package/CHANGELOG.md +123 -0
  2. package/EDITOR.md +2 -2
  3. package/FAQ.md +55 -11
  4. package/README.md +6 -2
  5. package/REFERENCE.md +203 -57
  6. package/dist/littlejs.d.ts +933 -313
  7. package/dist/littlejs.esm.js +5143 -2419
  8. package/dist/littlejs.esm.min.js +38 -24
  9. package/dist/littlejs.js +5132 -2416
  10. package/dist/littlejs.min.js +37 -23
  11. package/dist/littlejs.release.js +4905 -2232
  12. package/package.json +4 -4
  13. package/plugins/box2d.js +141 -56
  14. package/plugins/drawUtilities.js +374 -380
  15. package/plugins/gltf.js +383 -60
  16. package/plugins/lightSystem.js +412 -30
  17. package/plugins/math3d.js +1015 -1014
  18. package/plugins/medalSystem.js +19 -7
  19. package/plugins/newgrounds.js +5 -6
  20. package/plugins/parallax.js +1 -0
  21. package/plugins/particleEffects.js +47 -14
  22. package/plugins/pathFinder.js +60 -38
  23. package/plugins/pluginExport.js +11 -0
  24. package/plugins/postProcess.js +200 -67
  25. package/plugins/render3d.js +493 -2391
  26. package/plugins/render3dDebug.js +5 -8
  27. package/plugins/render3dEditor.js +84 -9
  28. package/plugins/render3dExtras.js +122 -33
  29. package/plugins/render3dLevel.js +67 -8
  30. package/plugins/render3dMesh.js +1158 -0
  31. package/plugins/render3dObject.js +1451 -0
  32. package/plugins/render3dVoxels.js +15 -3
  33. package/plugins/textureSheet.js +9 -8
  34. package/plugins/threejs.js +1 -1
  35. package/plugins/tweenSystem.js +117 -87
  36. package/plugins/uiSystem.js +62 -28
  37. package/plugins/wavedash.js +229 -0
  38. package/src/engine.js +192 -40
  39. package/src/engineAudio.js +47 -27
  40. package/src/engineBuild.mjs +6 -1
  41. package/src/engineDebug.js +23 -162
  42. package/src/engineDraw.js +148 -43
  43. package/src/engineEditor.js +153 -40
  44. package/src/engineExport.js +0 -3
  45. package/src/engineInput.js +31 -25
  46. package/src/engineMath.js +38 -20
  47. package/src/engineObject.js +68 -50
  48. package/src/engineParticles.js +66 -18
  49. package/src/engineRelease.js +18 -15
  50. package/src/engineSettings.js +48 -9
  51. package/src/engineTileLayer.js +44 -18
  52. package/src/engineUtilities.js +56 -11
  53. package/src/engineWebGL.js +68 -22
package/REFERENCE.md CHANGED
@@ -82,6 +82,40 @@ let remaining = 36000;
82
82
  })();
83
83
  ```
84
84
 
85
+ ## Units and directions
86
+ - **Space:** world units, y up. Draws and objects take full sizes (a circle's diameter); only the math helpers and
87
+ lights take a radius, and their parameter says so
88
+ - **2D angles:** radians, clockwise: `EngineObject.angle`, `cameraAngle`, the `angle` of every draw and of a
89
+ `ParticleEmitter`; `Vector2.angle()` is 0 up
90
+ - **3D angles:** radians, `rotation3D` is pitch, yaw and roll, turning as three.js does, counter clockwise seen
91
+ down an axis; `Camera3D.fov` is radians; a 3D level file and the 3D editor's `setTransform` take degrees, as
92
+ does `ThreeJSPlugin.cameraFOV`
93
+ - **Per frame:** speeds and spins, `velocity`, `angleVelocity`, a particle's `speed` and `angleSpeed`, damping and
94
+ gravity, at the fixed 60 updates a second
95
+ - **Seconds:** `Timer`, `Tween`, `time`, a particle emitter's `emitTime` and `particleTime` and its `emitRate` (per
96
+ second) and `lifeTime`; a particle's `fadeRate` is a part of its life, not seconds
97
+ - **Materials:** `roughness`, `reflectivity` and `specular` run 0 to 1, `shininess` 1 up, about 10,000 a mirror (a roughness of 0 sets it far higher), and
98
+ `emissive` from 0 up
99
+ - **Milliseconds:** `gamepadVibrate`'s duration, `vibrate` patterns and `saveDataURL`'s `revokeTime`
100
+ - **Limits:** the GPU draws with 32-bit floats: keep the world within about ±10,000 units of the origin, and a
101
+ shader's `iTime` gets coarse in a game left running for days; the FAQ says more
102
+
103
+ ## What functions give back
104
+ - **Raycasts:** where the ray hits, a Vector2: `tileCollisionRaycast`, `lineTest`; how far along the ray, a number:
105
+ `raycastBox`, `raycastSphere`, `raycastPlane`, `HeightMap.raycast`; a record of the hit: `VoxelMap.raycast` (a
106
+ `VoxelHit`), `render3D.pick` (`{object, distance}`), `box2d.raycast` (a `Box2dRaycastResult`); every object
107
+ hit, a list: `engineObjectsRaycast`; nothing hit is `undefined`, or an empty list
108
+ - **Overlaps:** `isOverlapping` and `isIntersecting` say whether, a boolean, as does a layer's `collisionTest`;
109
+ `tileCollisionTest` gives the layer hit; the collide helpers (`collideCircleBox`, `collideSphereBox` and the rest)
110
+ give the push that moves the first shape out, each `undefined` for none
111
+ - **Not found:** `undefined`, but `PathFinder.getNode` and `getNearestClearNode`, which give `null`
112
+ - **Loading:** an image that fails, from `engineInit` or `loadTexture`, and a `Sound` made from a file that fails,
113
+ warn in the console and the game goes on without them; `fetchJSON`, `loadGLTF`, `loadOBJ` and `Sound.loadSound`
114
+ reject with an error naming the file, for the game to catch
115
+ - **Ending things:** objects, particles, UI, Box2D objects and joints are ended with `destroy`; a `Mesh` or a
116
+ `GLTFModel` frees its GPU buffers with `dispose`, a `TextureInfo` its texture with `destroyWebGLTexture`; a
117
+ playing sound, a `Tween` and an animation `stop`
118
+
85
119
  ## LittleJS Utilities Classes and Functions
86
120
  - General purpose math library
87
121
  - Vector2 - Fast, simple, easy 2D vector class
@@ -116,7 +150,7 @@ lerpAngle(angleA, angleB, percent) // Linearly interpolates with wrap
116
150
  smoothStep(percent) // Applies smoothstep function, percent clamped to 0-1
117
151
  isPowerOfTwo(value) // Checks if the value is a power of two
118
152
  nearestPowerOfTwo(value) // Smallest power of two not less than the value
119
- isOverlapping(pointA, sizeA, pointB, sizeB) // Checks if bounding boxes overlap
153
+ isOverlapping(posA, sizeA, posB, sizeB) // Checks if bounding boxes overlap
120
154
  isIntersecting(start, end, pos, size) // Checks if ray intersects box
121
155
  // the collide helpers answer how far out, where isOverlapping answers whether; boxes are axis aligned and centered
122
156
  // with a full size, and each returns undefined when the shapes are not touching
@@ -169,13 +203,13 @@ Vector2.length() // Get length
169
203
  Vector2.lengthSquared() // Get length squared
170
204
  Vector2.distance(v) // Get distance to vector
171
205
  Vector2.distanceSquared(v) // Get distance to vector squared
172
- Vector2.normalize(length=1) // Normalize this vector to length
173
- Vector2.clampLength(length=1) // Clamp this vector to length
206
+ Vector2.normalize(length=1) // Copy of this vector at a length, its direction kept
207
+ Vector2.clampLength(length=1) // Copy of this vector no longer than a length
174
208
  Vector2.dot(v) // Dot product with vector
175
209
  Vector2.cross(v) // Cross product with vector
176
210
  Vector2.reflect(normal, restitution=1) // Reflect off a surface normal
177
- Vector2.floor() // Floor this vector
178
- Vector2.round() // Round this vector
211
+ Vector2.floor() // Copy with each component floored
212
+ Vector2.round() // Copy with each component rounded
179
213
  Vector2.abs() // Get copy with absolute value components
180
214
  Vector2.snap(grid) // Snap down to the grid, grid is steps per unit
181
215
  Vector2.mod(divisor=1) // Get modulo of each component
@@ -188,6 +222,8 @@ Vector2.rotate(angle) // Rotate by angle
188
222
  Vector2.setDirection(direction, length=1) // Set integer direction (0-3) and length
189
223
  Vector2.direction() // Get integer direction (0-3)
190
224
  Vector2.toString(digits=3) // Get string representation
225
+ // every Vector2 and Color method gives a new one and leaves it as it is, but set, setFrom, setAngle and
226
+ // setDirection, and Color's set, setFrom, setAlpha, setHSLA and setHex, which change it in place
191
227
 
192
228
  // RGBA color object
193
229
  Color(r=1, g=1, b=1, a=1) // Create an RGBA color
@@ -199,11 +235,11 @@ Color.subtract(c) // Subtract a color
199
235
  Color.multiply(c) // Multiply by a color
200
236
  Color.divide(c) // Divide by a color
201
237
  Color.scale(scale, alphaScale=scale) // Scale by a float
202
- Color.clamp() // Clamp this color
238
+ Color.clamp() // Copy with each component clamped to 0 to 1
203
239
  Color.lerp(c, percent) // Interpolate between colors
204
240
  Color.setHSLA(h=0, s=0, l=1, a=1) // Set the color from HSLA values
205
241
  Color.HSLA() // Get the color in HSLA format
206
- Color.mutate(amount=.05, alphaAmount=0) // Randomly diverge from this color
242
+ Color.mutate(amount=.05, alphaAmount=0) // Copy randomly diverged from this color
207
243
  Color.setHex(hex) // Set this color from a hex code
208
244
  Color.setAlpha(a=1) // Set the alpha of this color
209
245
  Color.withAlpha(a=1) // Get a copy of this color with the alpha set
@@ -267,6 +303,14 @@ drawEllipseGradient(pos, size=(1,1), colorInner=WHITE, colorOuter=CLEAR_WHITE, a
267
303
  drawCircleGradient(pos, size=1, colorInner=WHITE, colorOuter=CLEAR_WHITE)
268
304
  drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpace=drawScreenSpace, context)
269
305
 
306
+ // Where the orders differ, worth a look when changing one draw for another:
307
+ // - angle is after the colors in drawTile, drawRect, drawEllipse and the gradients, after size in drawCanvas2D,
308
+ // after lineColor in drawRegularPoly and after pos in drawLine, drawLineList and drawPoly; drawCircle has none
309
+ // - a line's width is before its color in drawLine and drawLineList, the outline's after the fill color, with
310
+ // lineColor, in drawPoly, drawRegularPoly, drawEllipse, drawCircle and the text
311
+ // - pos comes first in most draws, but is an offset near the end in drawLine, drawLineList and drawPoly
312
+ // - drawText and drawTextScreen draw with Canvas2D and take no useWebGL or screenSpace
313
+
270
314
  // Text functions
271
315
  drawText(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font, fontStyle, maxWidth, angle=0)
272
316
  drawTextScreen(text, pos, size, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font, fontStyle, maxWidth, angle=0)
@@ -283,6 +327,9 @@ new Shader(fragmentCode) // fragmentCode defines void mainImage(out vec
283
327
  // style; it gives the surface color, then the object's color and additive color
284
328
  // apply in 2D, and the lighting, shadows and fog in 3D
285
329
  obj.shader = shader // any EngineObject or EngineObject3D; draws that share a Shader share a batch
330
+ shader.dispose() // frees its programs and takes it off the engine's list, which keeps every Shader
331
+ // for a lost context; for one made each time a scene starts; drawn again it
332
+ // compiles again, so let go of only one nothing draws with
286
333
  // names in the snippet: iChannel0 the texture, iTime, iResolution, localUV 0 to 1 across the sprite or the mesh uv,
287
334
  // premultipliedTexture true for a render target or a smooth image (tilesPixelated false), whose rgb and alpha
288
335
  // change together;
@@ -407,7 +454,9 @@ glDraw(x, y, sizeX, sizeY, angle=0, uv0X, uv0Y, uv1X, uv1Y, rgba=-1, rgbaAdditiv
407
454
  ```javascript
408
455
  // Sound Object
409
456
  Sound(zzfxSound, randomness, range, taper, onloadCallback) // Create a zzfx sound
410
- Sound(filename, randomness, range, taper, onloadCallback) // Load a wave, mp3, or ogg
457
+ zzfx(...zzfxSound) // Generate and play a zzfx sound at once, no Sound kept
458
+ Sound(filename, randomness, range, taper, onloadCallback) // Load a wave, mp3, or ogg; onloadCallback(sound) is
459
+ // called once it loads or fails to, isLoaded says which
411
460
  Sound.play(pos, volume=1, pitch=1, randomnessScale=1, loop=false, paused=false) // Play a sound, returns SoundInstance
412
461
  Sound.playLoop(pos, volume=1, pitch=1, randomnessScale=1, paused=false) // Play on a loop, like play with loop on
413
462
  Sound.playMusic(volume=1, loop=true, paused=false) // Play as music with looping
@@ -440,7 +489,8 @@ speakStop() // Stop all queued speech
440
489
  getNoteFrequency(semitoneOffset, rootFrequency=220) // Get frequency for musical notes
441
490
 
442
491
  // Audio settings
443
- soundEnable = true // Should sound be enabled?
492
+ soundEnable = true // Should sound be enabled? set it before sounds are made, setSoundEnable(false)
493
+ // before they are, setSoundVolume(0) to mute while the game runs
444
494
  soundIgnoreSilentSwitch = false // play with an iPhone's silent switch on, as media, pausing the player's music;
445
495
  // off, the switch mutes the game; setSoundIgnoreSilentSwitch(true)
446
496
  soundVolume = .3 // Volume scale to apply to all sound
@@ -487,12 +537,12 @@ AudioFilter.node, AudioReverb.node, ... // Each effect's wrapped Web A
487
537
 
488
538
  ```javascript
489
539
  // Keyboard, keys are KeyboardEvent.code names like 'KeyW', 'Space' or 'ArrowUp', not characters
490
- keyIsDown(key) // Is key down?
491
- keyWasPressed(key) // Was key pressed this frame?
492
- keyWasReleased(key) // Was key released this frame?
540
+ keyIsDown(key, device=0) // Is key down? device 0 is the keyboard and mouse, 1 on the gamepads
541
+ keyWasPressed(key, device=0) // Was key pressed this frame?
542
+ keyWasReleased(key, device=0) // Was key released this frame?
493
543
  keyDirection(up, down, left, right) // Get input vector from arrow keys or wasd
494
544
  inputClear() // Clear all input state
495
- inputClearKey(key) // Clear input state for a specific key
545
+ inputClearKey(key, device=0, clearDown=true, clearPressed=true, clearReleased=true) // Clear input state for a key
496
546
 
497
547
  // Mouse / Touch
498
548
  mousePos // World space mouse position
@@ -638,7 +688,7 @@ EngineObject.clampSpeed // Hold each axis of velocity to objectMaxSpeed while
638
688
  EngineObject.parent / children // Set by addChild, a child is placed by its parent and sits out solid collision
639
689
 
640
690
  // Engine Object settings
641
- enablePhysicsSolver = true // Enable collisions, between objects and with tiles?
691
+ enablePhysicsSolver = true // Enable collisions, between objects and with tiles, and in 3D with solids and the level?
642
692
  objectDefaultMass = 1 // Default object mass for collisions
643
693
  objectDefaultDamping = 1 // Fraction of velocity kept each frame (1 keeps all)
644
694
  objectDefaultAngleDamping = 1 // Fraction of angular velocity kept each frame (1 keeps all)
@@ -713,9 +763,9 @@ TileCollisionLayer.collisionTest(pos, size=(0,0), object) // Like tileCollisionT
713
763
  TileCollisionLayer.collisionRaycast(posStart, posEnd, object, normal) // Like tileCollisionRaycast for this layer only
714
764
  TileCollisionLayer.isSolid = true // Solid layers block objects and particles, the solidOnly tests
715
765
  // skip the others
716
- tileCollisionGetData(pos) // Get tile collision data at pos
766
+ tileCollisionGetData(pos, solidOnly=true) // Get tile collision data at pos
717
767
  tileCollisionTest(pos, size=(0,0), object) // Check if collision should occur
718
- tileCollisionRaycast(posStart, posEnd, object, normal, solidOnly=true) // Where the ray meets the first tile hit,
768
+ tileCollisionRaycast(posStart, posEnd, callbackObject, normal, solidOnly=true) // Where the ray meets the first tile hit,
719
769
  // or undefined; a normal vec2 passed in is set to the surface's
720
770
  tileCollisionLayers // List of all tile collision layers
721
771
  tileLayersLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisionLayer, draw=true) // collisionLayer is the index
@@ -783,7 +833,7 @@ particleEffectsBuiltIn // the built-in names: fire, torch, smok
783
833
  // itself, a continuous one goes until destroyed
784
834
  'fire' // hue .08, continuous
785
835
  'torch' // hue .09, continuous
786
- 'smoke' // no hue, grey, continuous
836
+ 'smoke' // no hue, gray, continuous
787
837
  'steam' // no hue, white, continuous
788
838
  'explosion' // hue .12, one-shot
789
839
  'sparks' // hue .17, continuous
@@ -806,9 +856,11 @@ particleEffectsBuiltIn // the built-in names: fire, torch, smok
806
856
  'confetti' // hue 0 and .6, mixed, one-shot
807
857
  'splash' // hue .58, one-shot
808
858
  particleEffectApply(emitter, effect) // set a live 2D emitter to an effect
809
- particleEffectApply3D(emitter3D, effect) // the same for a ParticleEmitter3D, its particles, place and scale kept
810
- particleEffectFromEmitter(emitter) // a 2D emitter's settings as an effect, to save or build in 3D
811
- particleEffectsAddBehavior(name, update, update3D) // add a behavior effects can name, with 2D and 3D pushes
859
+ particleEffectApply3D(emitter, effect) // the same for a ParticleEmitter3D, its particles, place and scale kept
860
+ particleEffectFromEmitter(emitter, name='Effect') // a 2D emitter's settings as an effect, to save or build in 3D
861
+ particleEffectsAddBehavior(name, update, update3D, min=-2, max=2, value=1, description='') // add a behavior effects
862
+ // can name, with 2D and 3D pushes; min and max for the designer and to clamp
863
+ // a strength to, value to start at
812
864
  particleEffectsAdd(effects) // add effects to play by name, one of the same name replaces it
813
865
  particleEffectsGet(name) // an effect's data, any case, to change or build by hand
814
866
  await particleEffectsLoad(url) // load the file the particle designer's Save Library wrote, and add its effects
@@ -973,7 +1025,7 @@ uiSystem.drawSlice(slice, pos, size, color=WHITE) // Draw a TileSlice to the UI
973
1025
  uiSystem.nativeHeight // If set, UI coords are normalized to this height
974
1026
  uiSystem.destroyObjects() // Remove all UI elements
975
1027
  uiSystem.isMouseOverUI() // True if the mouse is over a visible hoverable UI object, or a confirm dialog is open
976
- uiSetDebug(enable) // Toggle uiDebug rendering of widget bounds
1028
+ uiSetDebug(debugMode) // Toggle uiDebug rendering of widget bounds
977
1029
 
978
1030
  // Confirm dialog
979
1031
  uiSystem.showConfirmDialog(text='Are you sure?', yes, no, size, exitKey='Escape') // the exit key or gamepad B answers no, the title and buttons scale with size
@@ -1083,6 +1135,20 @@ light.glow = 0 // size across of a soft hazy glow over the light, like
1083
1135
  // over the lit scene after the lightmap, so it shows in the dark, in front of everything
1084
1136
  light.glowFalloff = 1 // how fast the glow fades from its middle, .5 a wide haze, 2 a tight bright core
1085
1137
 
1138
+ // One directional light, a sun: lights the whole scene from one direction, added with the point lights
1139
+ new DirectionalLight(sunDirection=vec2(-1,1), color=WHITE) // toward the sun, as render3D.sunDirection; its color
1140
+ sun.sunDirection // toward the sun, it shines the other way
1141
+ sun.castShadow = true // foreground casters (castShadow) throw long shadows; no lightSystem.shadows needed
1142
+ sun.shadowLength = 20 // world units a long shadow reaches before it has faded out; a caster casts from inside
1143
+ // the shadow map only, (shadowMapScale - 1) / 2 of a view past the screen
1144
+ sun.backgroundDepth = 3 // world units the light gets into a background area from its edges facing it
1145
+ lightSystem.directionalLight // the one there is, or undefined; a second one asserts, destroy the first
1146
+ // (a release build destroys the first itself)
1147
+ lightSystem.directionalTextureSize = 512 // pixels across its work textures, which cover the shadow map's area; a
1148
+ // power of two, as shadowMapSize is, or the shadows shimmer as the view pans
1149
+ obj.castBackgroundShadow = false // a background: dark to the directional light inside, lit at its edges;
1150
+ // a background layer sets castShadow = false and this true
1151
+
1086
1152
  // Per-object lightmap contribution hook (on every EngineObject)
1087
1153
  class LavaTile extends EngineObject {
1088
1154
  renderLight() {
@@ -1129,6 +1195,16 @@ postProcessChromatic(strength=.005) // red and blue split apart toward
1129
1195
  postProcessGlow(threshold=.6, strength=1, size=6) // the bloom as a piece
1130
1196
  postProcessOutline(color=BLACK, thickness=1, threshold=.02) // lines where the 3D depth jumps, needs
1131
1197
  // render3D.depthTexture
1198
+ postProcessTiltShift(focus=.5, size=.25, blur=8) // a sharp band across the screen at focus, 0 bottom to 1 top,
1199
+ // size its height, blurred above and below, 2D or 3D
1200
+ postProcessDepthOfField(focus=10, range=4, blur=8) // sharp at focus world units from the camera, range deep,
1201
+ // blurred nearer and farther; needs render3D.depthTexture
1202
+ postProcess.values = {focus: .5} // the game's own values for the shader, a uniform each, set every
1203
+ // frame; any effect setting but glow's size, which sets its sample
1204
+ // count, may be such a name, to change it live; set them after the
1205
+ // plugin is made, as the shader is made at the first render;
1206
+ // not named _x, iX, gl_x, c, uv or p, which the shader uses:
1207
+ new PostProcessPlugin(postProcessEffects(postProcessTiltShift('focus'))); postProcess.values.focus = .6; // live
1132
1208
  ```
1133
1209
 
1134
1210
  ## LittleJS 3D Math
@@ -1164,7 +1240,7 @@ m.invert() m.transpose() // in place, return self
1164
1240
  m.copy() m.transformPoint(v) m.transformDirection(v) m.getTranslation() // or v.transform(m), v.transformDirection(m)
1165
1241
  m.getScale() m.getRotation() // the scale and the vec3(pitch, yaw, roll) back out of a transform, a mirror is a negative x
1166
1242
  m.determinant() // of the rotation and scale part: negative when it mirrors, 0 when it flattens
1167
- buildMatrix(pos, rotation, scale, out) // translate * rotate * scale, any arg optional; out is written into
1243
+ buildMatrix(pos, rotation, scale, matrix) // translate * rotate * scale, any arg optional; matrix is written into
1168
1244
  // instead of a new matrix, for a loop that builds many each frame
1169
1245
 
1170
1246
  // Collision - boxes are centered with full size, upright unless given a rotation, an Euler vec3 like rotation3D;
@@ -1343,6 +1419,19 @@ render3D.setSky(topColor, horizonColor, bottomColor, ambient=.5) // dome colors
1343
1419
  // color from below, both times ambient; 0 leaves ambient dark
1344
1420
  render3D.setFog(fogStart, fogEnd, fogColor) // the fog distances and color at once, no color keeps the current one
1345
1421
  render3D.sky = buildSky(topColor, horizonColor, bottomColor, sides, rings) // or set a dome yourself
1422
+ render3D.skyBox = cubeMap // a CubeMap drawn behind everything in place of the dome, undefined by default;
1423
+ // fog fades to fogColor, so set it to the sky box's horizon color
1424
+ render3D.environment = cubeMap // what reflective surfaces reflect, undefined reflects the sky's colors; one
1425
+ // made again and again disposes the one it replaces, its GPU memory
1426
+ makeCubeMap(size, colorOf) // a CubeMap of six size by size faces, colorOf(direction) the Color each way,
1427
+ // a unit Vector3; paint a sky in code, 64 or so is plenty for a blurry one
1428
+ await loadCubeMap([px, nx, py, ny, pz, nz]) // a CubeMap from six square image urls, +x, -x, +y, -y, +z and -z,
1429
+ // a sky box set shown as three.js shows it, each face as its image is drawn
1430
+ cubeMap.size cubeMap.faces cubeMap.dispose() // its face size, its six faces, and freeing its GPU texture
1431
+ const mirror = new CubeMap(128) // no faces: a cube map to draw the scene into
1432
+ mirror.capture(pos3D) // draw the scene around pos3D into it in the next frame's pass, six views of
1433
+ // the whole scene: once for a still scene, every few frames for a moving one
1434
+ mirror.capturePos // where it was last captured from, captured again from there after a lost context
1346
1435
 
1347
1436
  // Draw state, read at each draw; the pass sets it from each object's flags before render3D() and resets it before each
1348
1437
  // callback, so set it inside those, or use the object flags below
@@ -1354,6 +1443,7 @@ render3D.specular = 0 // Phong highlight strength, the shiny spo
1354
1443
  // out; shininess sets the size of the spot
1355
1444
  render3D.shininess = 16 // the highlight's exponent, 4 broad like rubber, 100 sharp like polished metal
1356
1445
  render3D.normalMap render3D.normalScale render3D.reflectivity render3D.emissiveMap render3D.emissiveMapColor
1446
+ render3D.environmentMap // the cube map reflected in place of render3D.environment, from obj.environment
1357
1447
  // the material, set from each object's fields of the same names
1358
1448
  render3D.receiveShadow = true // false keeps the next draws out of the shadow map's darkening
1359
1449
  render3D.shader = undefined // a Shader for the next draws, set from each object's shader; with emissive 1
@@ -1474,13 +1564,20 @@ obj.emissive = 1 // 0 by default; how much it lights itse
1474
1564
  // things, between partly self lit, above 1 brighter for bloom; still casts
1475
1565
  obj.specular = .5 // highlight strength, 0 is none and 1 is full, as render3D.specular; 0 by default
1476
1566
  obj.shininess = 100 // 16 by default; the highlight's exponent, higher is smaller and sharper
1567
+ obj.roughness = .5 // shininess set the way glTF measures it, 0 a mirror to 1 matte: it sets
1568
+ // shininess = 2 / roughness^4 - 2, at least 1, and reads it back, as
1569
+ // .9 from about .9 up, where shininess is 1
1477
1570
  obj.normalMap = textureInfo // bumps and grooves that catch the light, read at the color texture's uvs;
1478
1571
  // green points up the image (OpenGL and glTF), flip the green of a DirectX one;
1479
1572
  // no tangents needed, any mesh with uvs works; normalMapFromHeight makes one
1480
1573
  obj.normalScale = 1 // how strongly it bends the surface, 0 turns it off
1481
- obj.reflectivity = .5 // 0 by default, 1 a mirror of the sky; more at a glancing angle (Fresnel);
1482
- // shows the colors of render3D.sky from setSky or buildSky, or the ambient
1483
- // ones with no sky; not the scene
1574
+ obj.reflectivity = .5 // 0 by default, 1 a mirror; more at a glancing angle (Fresnel); shows
1575
+ // render3D.environment, sharper the higher shininess, as the highlight
1576
+ // is: 10000 a mirror, 1000 polished, 10 a wide blur;
1577
+ // with none, the colors of render3D.sky from setSky or buildSky, or the
1578
+ // ambient ones with no sky
1579
+ obj.environment = mirror // a CubeMap it reflects in place of render3D.environment, as one captured
1580
+ // from its own middle; undefined by default
1484
1581
  obj.emissiveMap = textureInfo // where it glows, added on top of the lit surface so it shows in the dark
1485
1582
  obj.emissiveMapColor = WHITE // multiplies the emissive map
1486
1583
  obj.castShadow = false // true by default, false keeps it out of the shadow map; sprites and cut out
@@ -1687,8 +1784,9 @@ await loadOBJ(url, smooth) // fetch then parse, in an async gameInit; chain .cen
1687
1784
  // units
1688
1785
 
1689
1786
  // glTF models - the glTF plugin, .gltf with its files beside it or .glb in one file; meshes with their node placement,
1690
- // vertex colors, material colors and base color textures, and node animations; no skins or morph targets, and no
1691
- // Draco or meshopt compressed geometry, which throws saying so
1787
+ // vertex colors, material colors and base color textures, node animations and skinned characters, four joints a
1788
+ // vertex, bent on the CPU each frame; no morph targets, and no Draco or meshopt compressed geometry, which throws
1789
+ // saying so
1692
1790
  const model = await loadGLTF(url) // a GLTFModel, in an async gameInit; or await parseGLTF(data, baseUrl, files) on
1693
1791
  // bytes or JSON you already have; files, a Map of the .bin and image files a
1694
1792
  // .gltf names by their paths, like the files of a drop, in place of fetching,
@@ -1700,7 +1798,7 @@ model.parts // one GLTFPart per primitive of every node
1700
1798
  // are the set the texture's texCoord names, moved by KHR_texture_transform;
1701
1799
  // normalMap and normalScale from normalTexture, emissiveMap and
1702
1800
  // emissiveMapColor from emissiveTexture and emissiveFactor, read at the base
1703
- // color texture's uvs
1801
+ // color texture's uvs, and roughness from roughnessFactor, 1 when it has none
1704
1802
  model.mesh, model.textureInfo // everything as one Mesh tinted by its materials, and its texture when every
1705
1803
  // part uses the same one; a model mixing plain and textured parts, or using
1706
1804
  // several textures or unlit parts, draws right through createObject
@@ -1709,15 +1807,20 @@ model.createObject(pos3D) // a GLTFObject, an EngineObject3D with a c
1709
1807
  // emissive 1 for an unlit one; move and turn the root and the parts follow
1710
1808
  model.animations // one GLTFAnimation each: name, duration in seconds, and the channels that
1711
1809
  // move, turn and scale nodes; model.getAnimation(nameOrNumber) finds one
1712
- object.play(animation=0, loop=true, speed=1) // play one on a GLTFObject by name or number, its parts move with it;
1713
- // speed below 0 plays it backward, and one that does not loop holds its end
1810
+ object.play(animation=0, loop=true, speed=1, blend=0) // play one on a GLTFObject by name or number, its parts move
1811
+ // and a skinned mesh bends with it; speed below 0 plays it backward, one that
1812
+ // does not loop holds its end, and blend is seconds to cross-fade from the pose
1813
+ // it is in, the animation it leaves going on through the fade
1814
+ object.getJointMatrix(name) // a node's world Matrix4 as posed now, by its name in the file, to hang a sword
1815
+ // on a hand; undefined for a name the model does not have
1714
1816
  object.stop() // hold the pose where it is; object.setAnimationTime(t) poses it at a time
1715
1817
  object.animation .animationTime .animationSpeed .animationLoop .animationPlaying
1716
1818
  model.getPose(animation, time) // one Matrix4 per part, how far it moved from its resting place
1717
1819
  model.dispose() // free the part meshes, the combined mesh and the textures of a model that
1718
1820
  // is done with; destroy the objects createObject made first
1719
1821
  // colors come in converted from glTF's linear values, a NEAREST sampler makes a part pixelated, sparse accessors
1720
- // are read, and object.parts is what an animation poses
1822
+ // are read, and object.parts is what an animation poses; a skinned part's object bends a mesh of its own, so
1823
+ // characters of one model hold their own poses, and part.skin is what bends it
1721
1824
  model.center().fit(size) // move the model's bounds onto the origin and scale its largest extent to
1722
1825
  // size, every part together, like Mesh.center and fit; getBounds and
1723
1826
  // transform(matrix) as well
@@ -1738,6 +1841,7 @@ emitter3D.gravityScale = 0 // a share of render3D.gravity added on top of its ow
1738
1841
  // an emitter with an emitTime destroys itself once its last particle is gone, so a burst is fire and forget
1739
1842
  // untextured particles are soft round dots, textured ones are billboards of the tile
1740
1843
  emitter.collideLevel = false // particles hit the height maps and voxel maps, bounce by restitution, slide by friction
1844
+ // only: not solid objects, as 2D particles hit only tile layers
1741
1845
  emitter.particleCreateCallback / particleUpdateCallback / particleCollideCallback / particleDestroyCallback
1742
1846
  // as in 2D, each given a Particle3D {pos, velocity, age, lifeTime, emitter, destroy()}, one object
1743
1847
  // the emitter reuses, so copy what you keep; the collide callback gets (particle, level, pos) and
@@ -1818,7 +1922,9 @@ material.side = THREE.DoubleSide // mesh.doubleSided = true
1818
1922
  material.emissiveIntensity // obj.emissive
1819
1923
  material.normalMap, normalScale // obj.normalMap, obj.normalScale (a number, three.js takes a Vector2)
1820
1924
  material.shininess (MeshPhongMaterial) // obj.shininess, with obj.specular the strength
1821
- material.envMap, reflectivity // obj.reflectivity, which reflects the sky's colors, not an environment map
1925
+ material.reflectivity // obj.reflectivity, with render3D.environment or obj.environment the map
1926
+ material.metalness // none; a dark color with reflectivity reads as metal
1927
+ material.roughness (MeshStandardMaterial) // obj.roughness, which sets obj.shininess: 0 a mirror, 1 matte
1822
1928
  material.emissiveMap, emissive // obj.emissiveMap, obj.emissiveMapColor
1823
1929
  material.transparent, blending // obj.transparent, obj.additive
1824
1930
  new THREE.ShaderMaterial({fragmentShader}) // obj.shader = new Shader(code), a mainImage snippet the engine wraps;
@@ -1833,15 +1939,20 @@ new THREE.SpotLight(color, i, d, angle, penumbra) // a Light3D with coneAngle =
1833
1939
  // for its castShadow
1834
1940
  light.castShadow, light.shadow.camera // render3D.shadows, shadowRange and shadowCenter
1835
1941
  scene.fog = new THREE.Fog(c, near, far) // render3D.setFog(near, far, c)
1836
- scene.background // render3D.setSky(topColor, horizonColor, bottomColor)
1942
+ scene.background // render3D.setSky(topColor, horizonColor, bottomColor), or
1943
+ // render3D.skyBox for a cube map
1944
+ scene.environment // render3D.environment
1945
+ material.envMap on one mesh // obj.environment
1946
+ new THREE.CubeCamera(near, far, target) // new CubeMap(size), then cubeMap.capture(pos3D) in place of update
1947
+ new THREE.CubeTextureLoader().load(urls) // await loadCubeMap(urls), the same order
1837
1948
  OrbitControls // new CameraControl3D(target, distance)
1838
1949
  PointerLockControls // new FirstPersonCamera3D
1839
1950
  new THREE.Raycaster() // render3D.screenToRay, pick and engineObjectsRaycast3D
1840
1951
  OBJLoader // loadOBJ(url) or parseOBJ(text)
1841
1952
  GLTFLoader // loadGLTF(url): model.createObject(pos) is the scene as objects, model.mesh
1842
1953
  // is everything as one Mesh
1843
- mixer.clipAction(clip).play() // object.play(name) on the object createObject made, node animation only,
1844
- // no skinned characters
1954
+ mixer.clipAction(clip).play() // object.play(name) on the object createObject made, skinned characters
1955
+ crossFadeTo(next, seconds) // object.play(next, true, 1, seconds); bone.getWorldPosition: getJointMatrix
1845
1956
  EffectComposer and UnrealBloomPass // postProcessBloom()
1846
1957
  renderer.render(scene, camera) // nothing to do, the engine draws every frame and handles resizing
1847
1958
  position.setUsage(THREE.DynamicDrawUsage) // mesh.dynamicDraw = true once, then mesh.dirty = true when the points
@@ -1879,7 +1990,11 @@ level3DVoxelSetup(tileInfo, (map)=> {}) // The sheet a level's block m
1879
1990
  "fog": [25, 70], "fogColor": "#d6ecff", // start and end, an end of 0 for none; the horizon color
1880
1991
  // when the level has a sky and no fogColor
1881
1992
  "shadows": true,
1882
- "lensFlare": true}, // the sun's lens flare, a LensFlare3D made for it
1993
+ "lensFlare": true, // the sun's lens flare, a LensFlare3D made for it
1994
+ "skyBox": ["px.png", "nx.png", "py.png", "ny.png", "pz.png", "nz.png"], // render3D.skyBox, six images
1995
+ "environment": ["px.png", "nx.png", "py.png", "ny.png", "pz.png", "nz.png"]}, // as loadCubeMap takes
1996
+ // them, loaded in the background, the same urls once;
1997
+ // urls are from the page, not from the level's file
1883
1998
  "objects": []}
1884
1999
 
1885
2000
  // A level can hold a map of blocks, made a VoxelMap when it loads: its corner, its size in cells, and its blocks
@@ -1895,7 +2010,8 @@ level3DVoxelSetup(tileInfo, (map)=> {}) // The sheet a level's block m
1895
2010
  // Prefabs - a prefab is a small level, objects about its own origin, placed many times under one name. The level
1896
2011
  // editor saves one as it saves a level, so it is the prefab editor too, and there every instance follows its prefab
1897
2012
  level3DAddPrefab(name, prefab) // Add {objects: [...]} as a type; its objects may be of other prefabs
1898
- await level3DLoadPrefab(name, url) // Fetch a prefab file and add it
2013
+ await level3DLoadPrefab(name, url) // Fetch a prefab file and add it; a prefab used inside it is
2014
+ // named by its type, the game adds that one too
1899
2015
  level3DSpawn(type, pos3D, rotation3D, scale3D, properties) // Make one object of any type from code, a prefab's
1900
2016
  // instance or a plain type; rotation in radians
1901
2017
  prefab = new Prefab3D // what a prefab's type makes, a handle that draws nothing:
@@ -1994,6 +2110,12 @@ obj.syncMesh() // copy the 2D transform to the mesh
1994
2110
  read only, move it with `setPosition`, `setAngle` or `setTransform` and push it with velocities and forces
1995
2111
  - Joints, raycasting, polygon/circle/edge fixtures
1996
2112
  - Angular values are clockwise like `angle`: angular velocity, torque, joint angles and limits, motor speeds
2113
+ - The EngineObject's own physics fields do nothing on a Box2dObject, Box2D moves it: `velocity`, `angleVelocity`,
2114
+ `damping`, `angleDamping`, `mass`, `friction`, `restitution` and `gravityScale`. Use `getLinearVelocity`,
2115
+ `setLinearVelocity`, `setAngularVelocity`, `setLinearDamping`, `setAngularDamping`, `setMass`, the friction and
2116
+ restitution given to its shapes, and `setGravityScale`; a debug build warns once when one is set
2117
+ - A world made again after `engineObjectsDestroy` does not step exactly as a fresh one, as Box2D reuses its ids, so
2118
+ a replay or a lockstep game that needs the same steps every time reloads the page
1997
2119
  - See `examples/box2d/` for a full demo
1998
2120
 
1999
2121
  ```javascript
@@ -2036,8 +2158,8 @@ obj.setAwake(awake=true)
2036
2158
  obj.setFixedRotation(isFixed=true) // Stop the body from rotating
2037
2159
  obj.setBullet(isBullet=true) // Continuous collision for fast bodies, so they don't pass through thin ones
2038
2160
  obj.setSensor(isSensor=true) // The fixtures it has now detect contacts without colliding
2039
- obj.setLinearDamping(damping) // Box2D's damping, 0 is none, larger slows it faster
2040
- obj.setAngularDamping(damping)
2161
+ obj.setLinearDamping(damping) // Box2D's damping, a rate: 0 is none, larger slows it faster, no upper limit
2162
+ obj.setAngularDamping(damping) // the same for its turning, where an EngineObject's damping is 0 to 1
2041
2163
  obj.setGravityScale(scale=1)
2042
2164
  obj.setMassData(localCenter, mass, momentOfInertia) // undefined leaves that one as it is, inertia is about the
2043
2165
  // center of mass
@@ -2148,6 +2270,46 @@ newgrounds.resendUnlocks() // advanced: send the ones whose request di
2148
2270
  // the minute's session check does; one the server refused is not resent
2149
2271
  ```
2150
2272
 
2273
+ ## LittleJS Wavedash
2274
+ - Optional plugin for Wavedash achievements and leaderboards, the same shape as the Newgrounds one
2275
+ - Wavedash serves the game's page and puts its SDK in `window.Wavedash`, so nothing is bundled; off Wavedash every call
2276
+ does nothing and medals unlock as any do
2277
+ - Make the plugin at the end of `gameInit`: it calls `Wavedash.init()`, until which Wavedash keeps its loading screen
2278
+ - Achievements are made with the Wavedash CLI, `--description` required; numbering the identifiers (`ACH_01_...`)
2279
+ orders them, since Wavedash lists them by identifier
2280
+ - A game built with its own minifier keeps the SDK's names (`Wavedash`, `init`, `success`, `data`, `id` and the
2281
+ methods) and real booleans: the SDK throws on a renamed call or a 1 for true (Terser's booleans_as_integers); the
2282
+ engine's own builds keep both
2283
+
2284
+ ```javascript
2285
+ // the medals, each with the identifier of its achievement on Wavedash
2286
+ const medal_finish = new WavedashMedal(0, 'ACH_01_FINISH', 'Finish', 'Finish a level');
2287
+ function gameInit()
2288
+ {
2289
+ medalsInit('My Game');
2290
+ new WavedashPlugin({LEVEL_1: {lowerWins: true, display: 'milliseconds'}, HIGH_SCORE: {}}); // last, it calls init
2291
+ }
2292
+ // later, in game code
2293
+ medal_finish.unlock(); // Wavedash's toast on Wavedash, the engine's popup anywhere else
2294
+ wavedash.postScore('LEVEL_1', timeMs);
2295
+
2296
+ new WavedashMedal(id, achievement, name, description, icon, src) // a Medal that is also a Wavedash achievement; its
2297
+ // unlock is sent until Wavedash takes it, which it refuses until it has loaded
2298
+ // the player's, a moment after launch; saved unlocks are sent too; one refused
2299
+ // for a minute warns in the console with its identifier, a typo or not made
2300
+ medal.achievement // the identifier of its Wavedash achievement
2301
+ new WavedashPlugin(leaderboards) // sets the wavedash global, calls Wavedash.init and makes the leaderboards: by
2302
+ // name, lowerWins for times and golf (higher wins when left out) and display
2303
+ // 'number' (the default), 'seconds', 'milliseconds' or 'ticks' (60 a second)
2304
+ wavedash.isActive() // whether the game is on Wavedash, its SDK on the page
2305
+ await wavedash.postScore(name, score) // a whole number, milliseconds for a time, Wavedash keeping the best; true once
2306
+ // posted, false off Wavedash or when it failed or took over 15 seconds; a board
2307
+ // not in the table is made then, higher wins
2308
+ await wavedash.getScores(name, offset=0, limit=10, friendsOnly=false) // the entries as Wavedash gives them,
2309
+ // undefined off Wavedash or when they could not be read
2310
+ wavedash.leaderboards // each board's id promise by its name
2311
+ ```
2312
+
2151
2313
  ## LittleJS Drawing Utilities
2152
2314
  - Optional plugin: nine-slice and three-slice helpers for scalable UI panels, plus a crescent shape
2153
2315
  - World-space (WebGL or 2D) and screen-space (2D by default, useWebGL for WebGL) variants
@@ -2282,7 +2444,7 @@ setLevelEditor(new MyEditor) // Make it the one in use, before the editor open
2282
2444
  - Press Escape key to toggle debug overlay
2283
2445
  - Number keys toggle debug functions while the overlay is open: 1 physics, 2 tiles (each tile layer's bounds, the
2284
2446
  collision values on screen, and the tiles under the mouse; pressing 2 again steps through the layers one at a time,
2285
- then off), 3 particles, 4 raycasts, 5 gamepads, 6 sound, 7 screenshot, 8 video capture, 9 tweakables panel,
2447
+ then off), 3 particles, 4 raycasts, 5 gamepads, 6 sound, 7 screenshot, 9 tweakables panel,
2286
2448
  0 level editor
2287
2449
  - C, while the overlay is open, is a free camera for a 3D game: the mouse looks once captured or with the right
2288
2450
  button held, WASD and QE fly, Shift is faster and the wheel sets the speed; the game runs on and reads no keys or
@@ -2301,15 +2463,12 @@ debugPoint(pos, color, time, angle) // Draw debug point
2301
2463
  debugLine(posA, posB, color, width=.1, time) // Draw debug line
2302
2464
  debugPoly(pos, points, color=WHITE, time=0, angle=0, fill) // Draw debug polygon
2303
2465
  debugText(text, pos, size=1, color=WHITE, time=0, angle=0) // Draw debug text
2304
- debugOverlap(pA, sA, pB, sB, color) // Draw a debug overlap between two boxes
2466
+ debugOverlap(posA, sizeA, posB, sizeB, color) // Draw a debug overlap between two boxes
2305
2467
  // each debug draw also takes screenSpace as its last parameter, defaulting to drawScreenSpace; debugText takes a
2306
2468
  // font before it
2307
2469
  debugClear() // Clear all debug primitives
2308
2470
  debugScreenshot() // Save a screenshot at the end of this frame
2309
2471
  debugShowErrors() // Show full page error message when an error occurs
2310
- debugVideoCaptureStart() // Start capturing a video of the canvas
2311
- debugVideoCaptureStop() // Stop capturing and save the video to disk
2312
- debugVideoCaptureIsActive() // Is video currently being captured?
2313
2472
  createCanvasContext(width, height=width, willReadFrequently=false) // Offscreen canvas to draw into, returns its
2314
2473
  // 2D context; the canvas is context.canvas
2315
2474
  saveCanvas(canvas, filename='screenshot', type='image/png') // Save canvas to a file
@@ -2327,19 +2486,6 @@ debugTweakables = false // Is the tweakables panel shown? setDebugTweakables(sho
2327
2486
  debugWatermark // Should watermark with FPS appear in debug mode?
2328
2487
  ```
2329
2488
 
2330
- ## Deprecated
2331
-
2332
- Old names and argument orders that still work, marked `@deprecated` in the types; use the new ones, the old are
2333
- kept until 1.25 at least.
2334
-
2335
- ```javascript
2336
- obj.collideTiles // since 1.20, use obj.collideLevel, the same flag
2337
- weldJoint.setSpringDampingRatio(ratio) // since 1.20, use setDampingRatio, and getDampingRatio for the getter
2338
- newgrounds.logView() // since 1.20, does nothing, the view is logged when the plugin starts
2339
- drawNineSliceScreen(pos, size, startTile, borderSize, extraSpace, angle) // since 1.20, the order is now
2340
- drawThreeSliceScreen(pos, size, startTile, borderSize, extraSpace, angle) // (pos, size, startTile, color, ...)
2341
- ```
2342
-
2343
2489
  [LittleJS Engine](https://github.com/KilledByAPixel/LittleJS) Copyright 2021 Frank Force
2344
2490
 
2345
2491
  ![LittleJS Logo](examples/favicon.png)