littlejsengine 1.11.13 → 1.11.17
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/dist/littlejs.d.ts +1306 -66
- package/dist/littlejs.esm.js +3097 -269
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +253 -265
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +155 -260
- package/examples/box2d/game.js +7 -10
- package/examples/box2d/gameObjects.js +33 -33
- package/examples/box2d/index.html +6 -6
- package/examples/box2d/scenes.js +28 -28
- package/examples/breakout/game.js +1 -4
- package/examples/breakout/index.html +4 -4
- package/examples/breakoutTutorial/index.html +2 -2
- package/examples/htmlMenu/game.js +3 -6
- package/examples/htmlMenu/index.html +2 -2
- package/examples/module/index.html +1 -1
- package/examples/particles/index.html +1 -1
- package/examples/platformer/game.js +0 -3
- package/examples/platformer/index.html +8 -8
- package/examples/puzzle/game.js +0 -3
- package/examples/puzzle/index.html +2 -2
- package/examples/shorts/base.html +1 -1
- package/examples/starter/build.js +4 -4
- package/examples/starter/index.html +13 -13
- package/examples/stress/index.html +3 -2
- package/examples/uiSystem/game.js +1 -7
- package/examples/uiSystem/index.html +3 -3
- package/package.json +3 -3
- package/plugins/box2d.js +1552 -640
- package/plugins/{Box2D_v2.3.1_min.wasm.js → box2d.wasm.js} +1 -1
- package/plugins/newgrounds.js +7 -8
- package/plugins/pluginExport.js +51 -0
- package/plugins/postProcess.js +94 -93
- package/plugins/uiSystem.js +145 -161
- package/plugins/zzfxm.js +163 -0
- package/src/engine.js +15 -20
- package/src/engineAudio.js +74 -204
- package/src/engineBuild.js +21 -3
- package/src/engineDebug.js +104 -6
- package/src/engineDraw.js +27 -14
- package/src/engineExport.js +12 -4
- package/src/engineParticles.js +6 -1
- package/src/engineRelease.js +6 -1
- package/src/engineSettings.js +2 -2
- package/src/engineUtilities.js +5 -11
- package/src/engineWebGL.js +19 -7
- package/examples/starter/build/index.html +0 -2
- package/examples/starter/build/index.js +0 -1
- package/examples/starter/build/tiles.png +0 -0
- package/examples/starter/game.zip +0 -0
- /package/plugins/{Box2D_v2.3.1_min.wasm.wasm → box2d.wasm.wasm} +0 -0
package/dist/littlejs.release.js
CHANGED
|
@@ -37,7 +37,12 @@ function debugClear (){}
|
|
|
37
37
|
function debugScreenshot (){}
|
|
38
38
|
function debugSaveCanvas (){}
|
|
39
39
|
function debugSaveText (){}
|
|
40
|
-
function debugSaveDataURL(){}
|
|
40
|
+
function debugSaveDataURL(){}
|
|
41
|
+
function debugShowErrors(){}
|
|
42
|
+
function debugVideoCaptureIsActive(){ return false; }
|
|
43
|
+
function debugVideoCaptureStart (){}
|
|
44
|
+
function debugVideoCaptureStop (){}
|
|
45
|
+
function debugVideoCaptureUpdate(){}
|
|
41
46
|
/**
|
|
42
47
|
* LittleJS Utility Classes and Functions
|
|
43
48
|
* - General purpose math library
|
|
@@ -131,7 +136,7 @@ function distanceWrap(valueA, valueB, wrapSize=1)
|
|
|
131
136
|
* @returns {number}
|
|
132
137
|
* @memberof Utilities */
|
|
133
138
|
function lerpWrap(percent, valueA, valueB, wrapSize=1)
|
|
134
|
-
{ return
|
|
139
|
+
{ return valueA + clamp(percent) * distanceWrap(valueB, valueA, wrapSize); }
|
|
135
140
|
|
|
136
141
|
/** Returns signed wrapped distance between the two angles passed in
|
|
137
142
|
* @param {number} angleA
|
|
@@ -329,23 +334,17 @@ class RandomGenerator
|
|
|
329
334
|
///////////////////////////////////////////////////////////////////////////////
|
|
330
335
|
|
|
331
336
|
/**
|
|
332
|
-
* Create a 2d vector, can take
|
|
333
|
-
* @param {
|
|
334
|
-
* @param {number} [y]
|
|
337
|
+
* Create a 2d vector, can take 1 or 2 scalar values
|
|
338
|
+
* @param {number} [x]
|
|
339
|
+
* @param {number} [y] - if y is undefined, x is used for both
|
|
335
340
|
* @return {Vector2}
|
|
336
341
|
* @example
|
|
337
342
|
* let a = vec2(0, 1); // vector with coordinates (0, 1)
|
|
338
|
-
* let b = vec2(a); // copy a into b
|
|
339
343
|
* a = vec2(5); // set a to (5, 5)
|
|
340
344
|
* b = vec2(); // set b to (0, 0)
|
|
341
345
|
* @memberof Utilities
|
|
342
346
|
*/
|
|
343
|
-
function vec2(x=0, y)
|
|
344
|
-
{
|
|
345
|
-
return typeof x == 'number' ?
|
|
346
|
-
new Vector2(x, y == undefined? x : y) :
|
|
347
|
-
new Vector2(x.x, x.y);
|
|
348
|
-
}
|
|
347
|
+
function vec2(x=0, y) { return new Vector2(x, y == undefined? x : y); }
|
|
349
348
|
|
|
350
349
|
/**
|
|
351
350
|
* Check if object is a valid Vector2
|
|
@@ -1409,8 +1408,8 @@ function setSoundEnable(enable) { soundEnable = enable; }
|
|
|
1409
1408
|
function setSoundVolume(volume)
|
|
1410
1409
|
{
|
|
1411
1410
|
soundVolume = volume;
|
|
1412
|
-
if (soundEnable && !headlessMode &&
|
|
1413
|
-
|
|
1411
|
+
if (soundEnable && !headlessMode && audioMasterGain)
|
|
1412
|
+
audioMasterGain.gain.value = volume; // update gain immediately
|
|
1414
1413
|
}
|
|
1415
1414
|
|
|
1416
1415
|
/** Set default range where sound no longer plays
|
|
@@ -2076,6 +2075,8 @@ class TextureInfo
|
|
|
2076
2075
|
this.image = image;
|
|
2077
2076
|
/** @property {Vector2} - size of the image */
|
|
2078
2077
|
this.size = vec2(image.width, image.height);
|
|
2078
|
+
/** @property {Vector2} - inverse of the size, cached for rendering */
|
|
2079
|
+
this.sizeInverse = vec2(1/image.width, 1/image.height);
|
|
2079
2080
|
/** @property {WebGLTexture} - webgl texture */
|
|
2080
2081
|
this.glTexture = glEnable && glCreateTexture(image);
|
|
2081
2082
|
}
|
|
@@ -2121,19 +2122,19 @@ function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
|
|
|
2121
2122
|
* @param {Color} [color=(1,1,1,1)] - Color to modulate with
|
|
2122
2123
|
* @param {number} [angle] - Angle to rotate by
|
|
2123
2124
|
* @param {boolean} [mirror] - If true image is flipped along the Y axis
|
|
2124
|
-
* @param {Color} [additiveColor
|
|
2125
|
+
* @param {Color} [additiveColor] - Additive color to be applied if any
|
|
2125
2126
|
* @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
|
|
2126
2127
|
* @param {boolean} [screenSpace=false] - If true the pos and size are in screen space
|
|
2127
2128
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
2128
2129
|
* @memberof Draw */
|
|
2129
2130
|
function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
2130
|
-
angle=0, mirror, additiveColor
|
|
2131
|
+
angle=0, mirror, additiveColor, useWebGL=glEnable, screenSpace, context)
|
|
2131
2132
|
{
|
|
2132
2133
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
2133
2134
|
ASSERT(typeof tileInfo !== 'number' || !tileInfo,
|
|
2134
2135
|
'this is an old style calls, to fix replace it with tile(tileIndex, tileSize)');
|
|
2135
2136
|
ASSERT(isVector2(pos) && isVector2(size));
|
|
2136
|
-
ASSERT(isColor(color) && isColor(additiveColor));
|
|
2137
|
+
ASSERT(isColor(color) && (!additiveColor || isColor(additiveColor)));
|
|
2137
2138
|
|
|
2138
2139
|
const textureInfo = tileInfo && tileInfo.getTextureInfo();
|
|
2139
2140
|
if (useWebGL)
|
|
@@ -2144,21 +2145,30 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
|
2144
2145
|
pos = screenToWorld(pos);
|
|
2145
2146
|
size = size.scale(1/cameraScale);
|
|
2146
2147
|
}
|
|
2147
|
-
|
|
2148
2148
|
if (textureInfo)
|
|
2149
2149
|
{
|
|
2150
2150
|
// calculate uvs and render
|
|
2151
|
-
const sizeInverse =
|
|
2151
|
+
const sizeInverse = textureInfo.sizeInverse;
|
|
2152
2152
|
const x = tileInfo.pos.x * sizeInverse.x;
|
|
2153
2153
|
const y = tileInfo.pos.y * sizeInverse.y;
|
|
2154
2154
|
const w = tileInfo.size.x * sizeInverse.x;
|
|
2155
2155
|
const h = tileInfo.size.y * sizeInverse.y;
|
|
2156
|
-
const tileImageFixBleed = sizeInverse.scale(tileFixBleedScale);
|
|
2157
2156
|
glSetTexture(textureInfo.glTexture);
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2157
|
+
if (tileFixBleedScale)
|
|
2158
|
+
{
|
|
2159
|
+
const tileImageFixBleedX = sizeInverse.x*tileFixBleedScale;
|
|
2160
|
+
const tileImageFixBleedY = sizeInverse.y*tileFixBleedScale;
|
|
2161
|
+
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
2162
|
+
x + tileImageFixBleedX, y + tileImageFixBleedY,
|
|
2163
|
+
x - tileImageFixBleedX + w, y - tileImageFixBleedY + h,
|
|
2164
|
+
color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
|
|
2165
|
+
}
|
|
2166
|
+
else
|
|
2167
|
+
{
|
|
2168
|
+
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
2169
|
+
x, y, x + w, y + h,
|
|
2170
|
+
color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
|
|
2171
|
+
}
|
|
2162
2172
|
}
|
|
2163
2173
|
else
|
|
2164
2174
|
{
|
|
@@ -2370,16 +2380,15 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
|
|
|
2370
2380
|
function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, maxWidth=undefined, context=overlayContext)
|
|
2371
2381
|
{
|
|
2372
2382
|
context.fillStyle = color.toString();
|
|
2373
|
-
context.lineWidth = lineWidth;
|
|
2374
2383
|
context.strokeStyle = lineColor.toString();
|
|
2384
|
+
context.lineWidth = lineWidth;
|
|
2375
2385
|
context.textAlign = textAlign;
|
|
2376
2386
|
context.font = size + 'px '+ font;
|
|
2377
2387
|
context.textBaseline = 'middle';
|
|
2378
2388
|
context.lineJoin = 'round';
|
|
2379
2389
|
|
|
2380
|
-
pos = pos.copy();
|
|
2381
|
-
|
|
2382
2390
|
const lines = (text+'').split('\n');
|
|
2391
|
+
pos = pos.copy();
|
|
2383
2392
|
pos.y -= (lines.length-1) * size/2; // center text vertically
|
|
2384
2393
|
lines.forEach(line=>
|
|
2385
2394
|
{
|
|
@@ -2449,7 +2458,10 @@ class FontImage
|
|
|
2449
2458
|
{
|
|
2450
2459
|
// load default font image
|
|
2451
2460
|
if (!engineFontImage)
|
|
2452
|
-
|
|
2461
|
+
{
|
|
2462
|
+
engineFontImage = new Image;
|
|
2463
|
+
engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
|
|
2464
|
+
}
|
|
2453
2465
|
|
|
2454
2466
|
this.image = image || engineFontImage;
|
|
2455
2467
|
this.tileSize = tileSize;
|
|
@@ -3080,16 +3092,15 @@ let audioContext = new AudioContext;
|
|
|
3080
3092
|
/** Master gain node for all audio to pass through
|
|
3081
3093
|
* @type {GainNode}
|
|
3082
3094
|
* @memberof Audio */
|
|
3083
|
-
let
|
|
3095
|
+
let audioMasterGain;
|
|
3084
3096
|
|
|
3085
3097
|
function audioInit()
|
|
3086
3098
|
{
|
|
3087
3099
|
if (!soundEnable || headlessMode) return;
|
|
3088
3100
|
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
audioGainNode.gain.value = soundVolume; // set starting value
|
|
3101
|
+
audioMasterGain = audioContext.createGain();
|
|
3102
|
+
audioMasterGain.connect(audioContext.destination);
|
|
3103
|
+
audioMasterGain.gain.value = soundVolume; // set starting value
|
|
3093
3104
|
}
|
|
3094
3105
|
|
|
3095
3106
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -3218,6 +3229,8 @@ class Sound
|
|
|
3218
3229
|
isLoading() { return !this.sampleChannels; }
|
|
3219
3230
|
}
|
|
3220
3231
|
|
|
3232
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3233
|
+
|
|
3221
3234
|
/**
|
|
3222
3235
|
* Sound Wave Object - Stores a wave sound for later use and can be played positionally
|
|
3223
3236
|
* - this can be used to play wave, mp3, and ogg files
|
|
@@ -3269,59 +3282,7 @@ function playAudioFile(filename, volume=1, loop=false)
|
|
|
3269
3282
|
return new SoundWave(filename,0,0,0, s=>s.play(undefined, volume, 1, 1, loop));
|
|
3270
3283
|
}
|
|
3271
3284
|
|
|
3272
|
-
|
|
3273
|
-
* Music Object - Stores a zzfx music track for later use
|
|
3274
|
-
*
|
|
3275
|
-
* <a href=https://keithclark.github.io/ZzFXM/>Create music with the ZzFXM tracker.</a>
|
|
3276
|
-
* @example
|
|
3277
|
-
* // create some music
|
|
3278
|
-
* const music_example = new Music(
|
|
3279
|
-
* [
|
|
3280
|
-
* [ // instruments
|
|
3281
|
-
* [,0,400] // simple note
|
|
3282
|
-
* ],
|
|
3283
|
-
* [ // patterns
|
|
3284
|
-
* [ // pattern 1
|
|
3285
|
-
* [ // channel 0
|
|
3286
|
-
* 0, -1, // instrument 0, left speaker
|
|
3287
|
-
* 1, 0, 9, 1 // channel notes
|
|
3288
|
-
* ],
|
|
3289
|
-
* [ // channel 1
|
|
3290
|
-
* 0, 1, // instrument 0, right speaker
|
|
3291
|
-
* 0, 12, 17, -1 // channel notes
|
|
3292
|
-
* ]
|
|
3293
|
-
* ],
|
|
3294
|
-
* ],
|
|
3295
|
-
* [0, 0, 0, 0], // sequence, play pattern 0 four times
|
|
3296
|
-
* 90 // BPM
|
|
3297
|
-
* ]);
|
|
3298
|
-
*
|
|
3299
|
-
* // play the music
|
|
3300
|
-
* music_example.play();
|
|
3301
|
-
*/
|
|
3302
|
-
class Music extends Sound
|
|
3303
|
-
{
|
|
3304
|
-
/** Create a music object and cache the zzfx music samples for later use
|
|
3305
|
-
* @param {[Array, Array, Array, number]} zzfxMusic - Array of zzfx music parameters
|
|
3306
|
-
*/
|
|
3307
|
-
constructor(zzfxMusic)
|
|
3308
|
-
{
|
|
3309
|
-
super(undefined);
|
|
3310
|
-
|
|
3311
|
-
if (!soundEnable || headlessMode) return;
|
|
3312
|
-
this.randomness = 0;
|
|
3313
|
-
this.sampleChannels = zzfxM(...zzfxMusic);
|
|
3314
|
-
this.sampleRate = zzfxR;
|
|
3315
|
-
}
|
|
3316
|
-
|
|
3317
|
-
/** Play the music
|
|
3318
|
-
* @param {number} [volume=1] - How much to scale volume by
|
|
3319
|
-
* @param {boolean} [loop] - True if the music should loop
|
|
3320
|
-
* @return {AudioBufferSourceNode} - The audio source node
|
|
3321
|
-
*/
|
|
3322
|
-
playMusic(volume, loop=false)
|
|
3323
|
-
{ return super.play(undefined, volume, 1, 1, loop); }
|
|
3324
|
-
}
|
|
3285
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3325
3286
|
|
|
3326
3287
|
/** Speak text with passed in settings
|
|
3327
3288
|
* @param {string} text - The text to speak
|
|
@@ -3393,7 +3354,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
3393
3354
|
// create and connect gain node
|
|
3394
3355
|
gainNode = gainNode || audioContext.createGain();
|
|
3395
3356
|
gainNode.gain.value = volume;
|
|
3396
|
-
gainNode.connect(
|
|
3357
|
+
gainNode.connect(audioMasterGain);
|
|
3397
3358
|
|
|
3398
3359
|
// connect source to stereo panner and gain
|
|
3399
3360
|
const pannerNode = new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)});
|
|
@@ -3413,7 +3374,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
3413
3374
|
}
|
|
3414
3375
|
|
|
3415
3376
|
///////////////////////////////////////////////////////////////////////////////
|
|
3416
|
-
// ZzFXMicro - Zuper Zmall Zound Zynth - v1.3.
|
|
3377
|
+
// ZzFXMicro - Zuper Zmall Zound Zynth - v1.3.2 by Frank Force
|
|
3417
3378
|
|
|
3418
3379
|
/** Generate and play a ZzFX sound
|
|
3419
3380
|
*
|
|
@@ -3455,21 +3416,45 @@ const zzfxR = 44100;
|
|
|
3455
3416
|
*/
|
|
3456
3417
|
function zzfxG
|
|
3457
3418
|
(
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3419
|
+
volume = 1,
|
|
3420
|
+
randomness = .05,
|
|
3421
|
+
frequency = 220,
|
|
3422
|
+
attack = 0,
|
|
3423
|
+
sustain = 0,
|
|
3424
|
+
release = .1,
|
|
3425
|
+
shape = 0,
|
|
3426
|
+
shapeCurve = 1,
|
|
3427
|
+
slide = 0,
|
|
3428
|
+
deltaSlide = 0,
|
|
3429
|
+
pitchJump = 0,
|
|
3430
|
+
pitchJumpTime = 0,
|
|
3431
|
+
repeatTime = 0,
|
|
3432
|
+
noise = 0,
|
|
3433
|
+
modulation = 0,
|
|
3434
|
+
bitCrush = 0,
|
|
3435
|
+
delay = 0,
|
|
3436
|
+
sustainVolume = 1,
|
|
3437
|
+
decay = 0,
|
|
3438
|
+
tremolo = 0,
|
|
3439
|
+
filter = 0
|
|
3463
3440
|
)
|
|
3464
3441
|
{
|
|
3465
|
-
// LJS Note: ZZFX modded so randomness is handled by Sound class
|
|
3466
|
-
|
|
3467
3442
|
// init parameters
|
|
3468
|
-
let
|
|
3443
|
+
let sampleRate = zzfxR,
|
|
3444
|
+
PI2 = PI*2,
|
|
3469
3445
|
startSlide = slide *= 500 * PI2 / sampleRate / sampleRate,
|
|
3470
3446
|
startFrequency = frequency *=
|
|
3471
|
-
|
|
3472
|
-
|
|
3447
|
+
(1 + rand(randomness,-randomness)) * PI2 / sampleRate,
|
|
3448
|
+
modOffset = 0, // modulation offset
|
|
3449
|
+
repeat = 0, // repeat offset
|
|
3450
|
+
crush = 0, // bit crush offset
|
|
3451
|
+
jump = 1, // pitch jump timer
|
|
3452
|
+
length, // sample length
|
|
3453
|
+
b = [], // sample buffer
|
|
3454
|
+
t = 0, // sample time
|
|
3455
|
+
i = 0, // sample index
|
|
3456
|
+
s = 0, // sample value
|
|
3457
|
+
f, // wave frequency
|
|
3473
3458
|
|
|
3474
3459
|
// biquad LP/HP filter
|
|
3475
3460
|
quality = 2, w = PI2 * abs(filter) * 2 / sampleRate,
|
|
@@ -3479,35 +3464,37 @@ function zzfxG
|
|
|
3479
3464
|
b1 = -(sign(filter) + cos) / a0, b2 = b0,
|
|
3480
3465
|
x2 = 0, x1 = 0, y2 = 0, y1 = 0;
|
|
3481
3466
|
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3467
|
+
// scale by sample rate
|
|
3468
|
+
const minAttack = 9; // prevent pop if attack is 0
|
|
3469
|
+
attack = attack * sampleRate || minAttack;
|
|
3470
|
+
decay *= sampleRate;
|
|
3471
|
+
sustain *= sampleRate;
|
|
3472
|
+
release *= sampleRate;
|
|
3473
|
+
delay *= sampleRate;
|
|
3474
|
+
deltaSlide *= 500 * PI2 / sampleRate**3;
|
|
3475
|
+
modulation *= PI2 / sampleRate;
|
|
3476
|
+
pitchJump *= PI2 / sampleRate;
|
|
3477
|
+
pitchJumpTime *= sampleRate;
|
|
3478
|
+
repeatTime = repeatTime * sampleRate | 0;
|
|
3493
3479
|
|
|
3494
3480
|
// generate waveform
|
|
3495
3481
|
for(length = attack + decay + sustain + release + delay | 0;
|
|
3496
|
-
i < length; b[i++] = s * volume)
|
|
3482
|
+
i < length; b[i++] = s * volume) // sample
|
|
3497
3483
|
{
|
|
3498
|
-
if (!(++
|
|
3484
|
+
if (!(++crush%(bitCrush*100|0))) // bit crush
|
|
3499
3485
|
{
|
|
3500
|
-
s = shape? shape>1? shape>2? shape>3?
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
1-
|
|
3505
|
-
Math.
|
|
3486
|
+
s = shape? shape>1? shape>2? shape>3? shape>4? // wave shape
|
|
3487
|
+
(t/PI2%1 < shapeCurve/2? 1 : -1) : // 5 square duty
|
|
3488
|
+
Math.sin(t**3) : // 4 noise
|
|
3489
|
+
Math.max(Math.min(Math.tan(t),1),-1): // 3 tan
|
|
3490
|
+
1-(2*t/PI2%2+2)%2: // 2 saw
|
|
3491
|
+
1-4*abs(Math.round(t/PI2)-t/PI2): // 1 triangle
|
|
3492
|
+
Math.sin(t); // 0 sin
|
|
3506
3493
|
|
|
3507
3494
|
s = (repeatTime ?
|
|
3508
3495
|
1 - tremolo + tremolo*Math.sin(PI2*i/repeatTime) // tremolo
|
|
3509
3496
|
: 1) *
|
|
3510
|
-
sign(s)*
|
|
3497
|
+
(shape>4?s:sign(s)*abs(s)**shapeCurve) * // shape curve
|
|
3511
3498
|
(i < attack ? i/attack : // attack
|
|
3512
3499
|
i < attack + decay ? // decay
|
|
3513
3500
|
1-((i-attack)/decay)*(1-sustainVolume) : // decay falloff
|
|
@@ -3522,136 +3509,32 @@ function zzfxG
|
|
|
3522
3509
|
(i<length-delay? 1 : (length-i)/delay) * // release delay
|
|
3523
3510
|
b[i-delay|0]/2/volume) : s; // sample delay
|
|
3524
3511
|
|
|
3525
|
-
if (filter)
|
|
3512
|
+
if (filter) // apply filter
|
|
3526
3513
|
s = y1 = b2*x2 + b1*(x2=x1) + b0*(x1=s) - a2*y2 - a1*(y2=y1);
|
|
3527
3514
|
}
|
|
3528
3515
|
|
|
3529
3516
|
f = (frequency += slide += deltaSlide) *// frequency
|
|
3530
|
-
Math.cos(modulation*
|
|
3517
|
+
Math.cos(modulation*modOffset++); // modulation
|
|
3531
3518
|
t += f + f*noise*Math.sin(i**5); // noise
|
|
3532
3519
|
|
|
3533
|
-
if (
|
|
3520
|
+
if (jump && ++jump > pitchJumpTime) // pitch jump
|
|
3534
3521
|
{
|
|
3535
3522
|
frequency += pitchJump; // apply pitch jump
|
|
3536
3523
|
startFrequency += pitchJump; // also apply to start
|
|
3537
|
-
|
|
3524
|
+
jump = 0; // stop pitch jump time
|
|
3538
3525
|
}
|
|
3539
3526
|
|
|
3540
|
-
if (repeatTime && !(++
|
|
3527
|
+
if (repeatTime && !(++repeat % repeatTime)) // repeat
|
|
3541
3528
|
{
|
|
3542
|
-
frequency = startFrequency;
|
|
3543
|
-
slide = startSlide;
|
|
3544
|
-
|
|
3529
|
+
frequency = startFrequency; // reset frequency
|
|
3530
|
+
slide = startSlide; // reset slide
|
|
3531
|
+
jump ||= 1; // reset pitch jump time
|
|
3545
3532
|
}
|
|
3546
3533
|
}
|
|
3547
3534
|
|
|
3548
|
-
return b;
|
|
3535
|
+
return b; // return sample buffer
|
|
3549
3536
|
}
|
|
3550
|
-
|
|
3551
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
3552
|
-
// ZzFX Music Renderer v2.0.3 by Keith Clark and Frank Force
|
|
3553
|
-
|
|
3554
|
-
/** Generate samples for a ZzFM song with given parameters
|
|
3555
|
-
* @param {Array} instruments - Array of ZzFX sound parameters
|
|
3556
|
-
* @param {Array} patterns - Array of pattern data
|
|
3557
|
-
* @param {Array} sequence - Array of pattern indexes
|
|
3558
|
-
* @param {number} [BPM] - Playback speed of the song in BPM
|
|
3559
|
-
* @return {Array} - Left and right channel sample data
|
|
3560
|
-
* @memberof Audio */
|
|
3561
|
-
function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
3562
|
-
{
|
|
3563
|
-
let i, j, k;
|
|
3564
|
-
let instrumentParameters;
|
|
3565
|
-
let note;
|
|
3566
|
-
let sample;
|
|
3567
|
-
let patternChannel;
|
|
3568
|
-
let notFirstBeat;
|
|
3569
|
-
let stop;
|
|
3570
|
-
let instrument;
|
|
3571
|
-
let attenuation;
|
|
3572
|
-
let outSampleOffset;
|
|
3573
|
-
let isSequenceEnd;
|
|
3574
|
-
let sampleOffset = 0;
|
|
3575
|
-
let nextSampleOffset;
|
|
3576
|
-
let sampleBuffer = [];
|
|
3577
|
-
let leftChannelBuffer = [];
|
|
3578
|
-
let rightChannelBuffer = [];
|
|
3579
|
-
let channelIndex = 0;
|
|
3580
|
-
let panning = 0;
|
|
3581
|
-
let hasMore = 1;
|
|
3582
|
-
let sampleCache = {};
|
|
3583
|
-
let beatLength = zzfxR / BPM * 60 >> 2;
|
|
3584
|
-
|
|
3585
|
-
// for each channel in order until there are no more
|
|
3586
|
-
for (; hasMore; channelIndex++) {
|
|
3587
|
-
|
|
3588
|
-
// reset current values
|
|
3589
|
-
sampleBuffer = [hasMore = notFirstBeat = outSampleOffset = 0];
|
|
3590
|
-
|
|
3591
|
-
// for each pattern in sequence
|
|
3592
|
-
sequence.forEach((patternIndex, sequenceIndex) => {
|
|
3593
|
-
// get pattern for current channel, use empty 1 note pattern if none found
|
|
3594
|
-
patternChannel = patterns[patternIndex][channelIndex] || [0, 0, 0];
|
|
3595
|
-
|
|
3596
|
-
// check if there are more channels
|
|
3597
|
-
hasMore |= patterns[patternIndex][channelIndex]&&1;
|
|
3598
|
-
|
|
3599
|
-
// get next offset, use the length of first channel
|
|
3600
|
-
nextSampleOffset = outSampleOffset + (patterns[patternIndex][0].length - 2 - (notFirstBeat?0:1)) * beatLength;
|
|
3601
|
-
// for each beat in pattern, plus one extra if end of sequence
|
|
3602
|
-
isSequenceEnd = sequenceIndex == sequence.length - 1;
|
|
3603
|
-
for (i = 2, k = outSampleOffset; i < patternChannel.length + isSequenceEnd; notFirstBeat = ++i) {
|
|
3604
|
-
|
|
3605
|
-
// <channel-note>
|
|
3606
|
-
note = patternChannel[i];
|
|
3607
|
-
|
|
3608
|
-
// stop if end, different instrument or new note
|
|
3609
|
-
stop = i == patternChannel.length + isSequenceEnd - 1 && isSequenceEnd ||
|
|
3610
|
-
instrument != (patternChannel[0] || 0) || note | 0;
|
|
3611
|
-
|
|
3612
|
-
// fill buffer with samples for previous beat, most cpu intensive part
|
|
3613
|
-
for (j = 0; j < beatLength && notFirstBeat;
|
|
3614
|
-
|
|
3615
|
-
// fade off attenuation at end of beat if stopping note, prevents clicking
|
|
3616
|
-
j++ > beatLength - 99 && stop && attenuation < 1? attenuation += 1 / 99 : 0
|
|
3617
|
-
) {
|
|
3618
|
-
// copy sample to stereo buffers with panning
|
|
3619
|
-
sample = (1 - attenuation) * sampleBuffer[sampleOffset++] / 2 || 0;
|
|
3620
|
-
leftChannelBuffer[k] = (leftChannelBuffer[k] || 0) - sample * panning + sample;
|
|
3621
|
-
rightChannelBuffer[k] = (rightChannelBuffer[k++] || 0) + sample * panning + sample;
|
|
3622
|
-
}
|
|
3623
|
-
|
|
3624
|
-
// set up for next note
|
|
3625
|
-
if (note) {
|
|
3626
|
-
// set attenuation
|
|
3627
|
-
attenuation = note % 1;
|
|
3628
|
-
panning = patternChannel[1] || 0;
|
|
3629
|
-
if (note |= 0) {
|
|
3630
|
-
// get cached sample
|
|
3631
|
-
sampleBuffer = sampleCache[
|
|
3632
|
-
[
|
|
3633
|
-
instrument = patternChannel[sampleOffset = 0] || 0,
|
|
3634
|
-
note
|
|
3635
|
-
]
|
|
3636
|
-
] = sampleCache[[instrument, note]] || (
|
|
3637
|
-
// add sample to cache
|
|
3638
|
-
instrumentParameters = [...instruments[instrument]],
|
|
3639
|
-
instrumentParameters[2] *= 2 ** ((note - 12) / 12),
|
|
3640
|
-
|
|
3641
|
-
// allow negative values to stop notes
|
|
3642
|
-
note > 0 ? zzfxG(...instrumentParameters) : []
|
|
3643
|
-
);
|
|
3644
|
-
}
|
|
3645
|
-
}
|
|
3646
|
-
}
|
|
3647
|
-
|
|
3648
|
-
// update the sample offset
|
|
3649
|
-
outSampleOffset = nextSampleOffset;
|
|
3650
|
-
});
|
|
3651
|
-
}
|
|
3652
|
-
|
|
3653
|
-
return [leftChannelBuffer, rightChannelBuffer];
|
|
3654
|
-
}
|
|
3537
|
+
|
|
3655
3538
|
/**
|
|
3656
3539
|
* LittleJS Tile Layer System
|
|
3657
3540
|
* - Caches arrays of tiles to off screen canvas for fast rendering
|
|
@@ -4208,7 +4091,12 @@ class ParticleEmitter extends EngineObject
|
|
|
4208
4091
|
else
|
|
4209
4092
|
this.destroy();
|
|
4210
4093
|
|
|
4211
|
-
|
|
4094
|
+
if (debugParticles)
|
|
4095
|
+
{
|
|
4096
|
+
// show emitter bounds
|
|
4097
|
+
const emitSize = typeof this.emitSize === 'number' ? vec2(this.emitSize) : this.emitSize;
|
|
4098
|
+
debugRect(this.pos, emitSize, '#0f0', 0, this.angle);
|
|
4099
|
+
}
|
|
4212
4100
|
}
|
|
4213
4101
|
|
|
4214
4102
|
/** Spawn one particle
|
|
@@ -4670,12 +4558,12 @@ function glPreRender()
|
|
|
4670
4558
|
|
|
4671
4559
|
// set vertex attributes
|
|
4672
4560
|
let offset = glAdditive = glBatchAdditive = 0;
|
|
4673
|
-
|
|
4561
|
+
const initVertexAttribArray = (name, type, typeSize, size)=>
|
|
4674
4562
|
{
|
|
4675
4563
|
const location = glContext.getAttribLocation(glShader, name);
|
|
4676
4564
|
const stride = typeSize && gl_INSTANCE_BYTE_STRIDE; // only if not geometry
|
|
4677
4565
|
const divisor = typeSize && 1; // only if not geometry
|
|
4678
|
-
const normalize = typeSize==1; // only if color
|
|
4566
|
+
const normalize = typeSize == 1; // only if color
|
|
4679
4567
|
glContext.enableVertexAttribArray(location);
|
|
4680
4568
|
glContext.vertexAttribPointer(location, size, type, normalize, stride, offset);
|
|
4681
4569
|
glContext.vertexAttribDivisor(location, divisor);
|
|
@@ -4774,14 +4662,14 @@ function glCreateTexture(image)
|
|
|
4774
4662
|
const texture = glContext.createTexture();
|
|
4775
4663
|
glContext.bindTexture(glContext.TEXTURE_2D, texture);
|
|
4776
4664
|
if (image && image.width)
|
|
4777
|
-
|
|
4665
|
+
glSetTextureData(texture, image);
|
|
4778
4666
|
else
|
|
4779
4667
|
{
|
|
4780
4668
|
// create a white texture
|
|
4781
4669
|
const whitePixel = new Uint8Array([255, 255, 255, 255]);
|
|
4782
4670
|
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, 1, 1, 0, glContext.RGBA, glContext.UNSIGNED_BYTE, whitePixel);
|
|
4783
4671
|
}
|
|
4784
|
-
|
|
4672
|
+
|
|
4785
4673
|
// use point filtering for pixelated rendering
|
|
4786
4674
|
const filter = tilesPixelated ? glContext.NEAREST : glContext.LINEAR;
|
|
4787
4675
|
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, filter);
|
|
@@ -4789,6 +4677,18 @@ function glCreateTexture(image)
|
|
|
4789
4677
|
return texture;
|
|
4790
4678
|
}
|
|
4791
4679
|
|
|
4680
|
+
/** Set WebGL texture data from an image
|
|
4681
|
+
* @param {WebGLTexture} texture
|
|
4682
|
+
* @param {HTMLImageElement} image
|
|
4683
|
+
* @memberof WebGL */
|
|
4684
|
+
function glSetTextureData(texture, image)
|
|
4685
|
+
{
|
|
4686
|
+
// build the texture
|
|
4687
|
+
ASSERT(!!image && image.width > 0, 'Invalid image data.');
|
|
4688
|
+
glContext.bindTexture(glContext.TEXTURE_2D, texture);
|
|
4689
|
+
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, image);
|
|
4690
|
+
}
|
|
4691
|
+
|
|
4792
4692
|
/** Draw all sprites and clear out the buffer, called automatically by the system whenever necessary
|
|
4793
4693
|
* @memberof WebGL */
|
|
4794
4694
|
function glFlush()
|
|
@@ -4842,10 +4742,10 @@ function glSetAntialias(antialias=true)
|
|
|
4842
4742
|
* @param {Number} uv0Y
|
|
4843
4743
|
* @param {Number} uv1X
|
|
4844
4744
|
* @param {Number} uv1Y
|
|
4845
|
-
* @param {Number} rgba
|
|
4846
|
-
* @param {Number} [rgbaAdditive=0]
|
|
4745
|
+
* @param {Number} [rgba=-1] - white is -1
|
|
4746
|
+
* @param {Number} [rgbaAdditive=0] - black is 0
|
|
4847
4747
|
* @memberof WebGL */
|
|
4848
|
-
function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdditive=0)
|
|
4748
|
+
function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=-1, rgbaAdditive=0)
|
|
4849
4749
|
{
|
|
4850
4750
|
ASSERT(typeof rgba == 'number' && typeof rgbaAdditive == 'number', 'invalid color');
|
|
4851
4751
|
|
|
@@ -4898,7 +4798,7 @@ const engineName = 'LittleJS';
|
|
|
4898
4798
|
* @type {string}
|
|
4899
4799
|
* @default
|
|
4900
4800
|
* @memberof Engine */
|
|
4901
|
-
const engineVersion = '1.11.
|
|
4801
|
+
const engineVersion = '1.11.17';
|
|
4902
4802
|
|
|
4903
4803
|
/** Frames per second to update
|
|
4904
4804
|
* @type {number}
|
|
@@ -4986,16 +4886,11 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4986
4886
|
ASSERT(Array.isArray(imageSources), 'pass in images as array');
|
|
4987
4887
|
|
|
4988
4888
|
// allow passing in empty functions
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
gameUpdatePost = ()=>{};
|
|
4995
|
-
if (!gameRender)
|
|
4996
|
-
gameRender = ()=>{};
|
|
4997
|
-
if (!gameRenderPost)
|
|
4998
|
-
gameRenderPost = ()=>{};
|
|
4889
|
+
gameInit ||= ()=>{};
|
|
4890
|
+
gameUpdate ||= ()=>{};
|
|
4891
|
+
gameUpdatePost ||= ()=>{};
|
|
4892
|
+
gameRender ||= ()=>{};
|
|
4893
|
+
gameRenderPost ||= ()=>{};
|
|
4999
4894
|
|
|
5000
4895
|
// Called automatically by engine to setup render system
|
|
5001
4896
|
function enginePreRender()
|
|
@@ -5022,11 +4917,13 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5022
4917
|
const debugSpeedUp = debug && keyIsDown('Equal'); // +
|
|
5023
4918
|
const debugSpeedDown = debug && keyIsDown('Minus'); // -
|
|
5024
4919
|
if (debug) // +/- to speed/slow time
|
|
5025
|
-
frameTimeDeltaMS *= debugSpeedUp ?
|
|
4920
|
+
frameTimeDeltaMS *= debugSpeedUp ? 10 : debugSpeedDown ? .1 : 1;
|
|
5026
4921
|
timeReal += frameTimeDeltaMS / 1e3;
|
|
5027
4922
|
frameTimeBufferMS += paused ? 0 : frameTimeDeltaMS;
|
|
5028
4923
|
if (!debugSpeedUp)
|
|
5029
|
-
frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp
|
|
4924
|
+
frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp min framerate
|
|
4925
|
+
if (debug && debugVideoCaptureIsActive())
|
|
4926
|
+
frameTimeBufferMS = 0; // disable time smoothing when capturing video
|
|
5030
4927
|
|
|
5031
4928
|
updateCanvas();
|
|
5032
4929
|
|
|
@@ -5105,6 +5002,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5105
5002
|
}
|
|
5106
5003
|
}
|
|
5107
5004
|
|
|
5005
|
+
debugVideoCaptureUpdate();
|
|
5108
5006
|
requestAnimationFrame(engineUpdate);
|
|
5109
5007
|
}
|
|
5110
5008
|
|
|
@@ -5152,11 +5050,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5152
5050
|
|
|
5153
5051
|
// setup html
|
|
5154
5052
|
const styleRoot =
|
|
5155
|
-
'margin:0;
|
|
5156
|
-
'width:100vw;height:100vh;' + // fill the window
|
|
5157
|
-
'display:flex;' + // use flexbox
|
|
5158
|
-
'align-items:center;' + // horizontal center
|
|
5159
|
-
'justify-content:center;' + // vertical center
|
|
5053
|
+
'margin:0;' + // fill the window
|
|
5160
5054
|
'background:#000;' + // set background color
|
|
5161
5055
|
(canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
|
|
5162
5056
|
'user-select:none;' + // prevent hold to select
|
|
@@ -5179,7 +5073,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5179
5073
|
overlayContext = overlayCanvas.getContext('2d');
|
|
5180
5074
|
|
|
5181
5075
|
// set canvas style
|
|
5182
|
-
const styleCanvas = 'position:absolute'
|
|
5076
|
+
const styleCanvas = 'position:absolute;'+ // allow canvases to overlap
|
|
5077
|
+
'top:50%;left:50%;transform:translate(-50%,-50%)'; // center on screen
|
|
5183
5078
|
mainCanvas.style.cssText = overlayCanvas.style.cssText = styleCanvas;
|
|
5184
5079
|
if (glCanvas)
|
|
5185
5080
|
glCanvas.style.cssText = styleCanvas;
|
|
@@ -5190,12 +5085,12 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5190
5085
|
new Promise(resolve =>
|
|
5191
5086
|
{
|
|
5192
5087
|
const image = new Image;
|
|
5193
|
-
image.crossOrigin = 'anonymous';
|
|
5194
5088
|
image.onerror = image.onload = ()=>
|
|
5195
5089
|
{
|
|
5196
5090
|
textureInfos[textureIndex] = new TextureInfo(image);
|
|
5197
5091
|
resolve();
|
|
5198
5092
|
}
|
|
5093
|
+
image.crossOrigin = 'anonymous';
|
|
5199
5094
|
image.src = src;
|
|
5200
5095
|
})
|
|
5201
5096
|
);
|