littlejsengine 1.13.4 → 1.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/copyToGithub.bat +28 -0
- package/dist/littlejs.d.ts +346 -240
- package/dist/littlejs.esm.js +1370 -723
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +1350 -709
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +1333 -692
- package/examples/box2d/game.js +1 -1
- package/examples/box2d/gameObjects.js +1 -1
- package/examples/breakout/game.js +30 -25
- package/examples/electron/index.html +2 -2
- package/examples/index.html +207 -96
- package/examples/platformer/gameEffects.js +19 -18
- package/examples/platformer/gameLevel.js +6 -1
- package/examples/shorts/base.html +1 -1
- package/examples/shorts/flappyGame.js +2 -1
- package/examples/shorts/helloWorld.js +1 -1
- package/examples/shorts/music.js +106 -0
- package/examples/shorts/parallax.js +71 -0
- package/examples/shorts/piano.js +7 -8
- package/examples/shorts/postProcess.js +25 -8
- package/examples/shorts/shapes.js +1 -9
- package/examples/shorts/song.mp3 +0 -0
- package/examples/shorts/uiSystem.js +15 -8
- package/examples/starter/index.html +2 -2
- package/examples/stress/index.html +8 -3
- package/examples/style.css +14 -4
- package/examples/uiSystem/game.js +11 -11
- package/package.json +1 -1
- package/plugins/box2d.js +10 -8
- package/plugins/drawUtilities.js +5 -5
- package/plugins/newgrounds.js +6 -6
- package/plugins/pluginExport.js +1 -1
- package/plugins/uiSystem.js +103 -79
- package/plugins/zzfxm.js +10 -10
- package/reference.md +90 -54
- package/src/engine.js +22 -22
- package/src/engineAudio.js +284 -109
- package/src/engineBuild.js +9 -9
- package/src/engineDebug.js +26 -26
- package/src/engineDraw.js +148 -97
- package/src/engineExport.js +22 -16
- package/src/engineInput.js +57 -67
- package/src/engineMedals.js +25 -25
- package/src/engineObject.js +25 -22
- package/src/engineParticles.js +59 -59
- package/src/engineRelease.js +1 -1
- package/src/engineSettings.js +20 -13
- package/src/engineTileLayer.js +34 -34
- package/src/engineUtilities.js +62 -55
- package/src/engineWebGL.js +447 -65
- /package/examples/shorts/{playSound.js → sound.js} +0 -0
package/dist/littlejs.release.js
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
'use strict';
|
|
5
5
|
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
7
|
* LittleJS - The Tiny Fast JavaScript Game Engine
|
|
8
8
|
* MIT License - Copyright 2021 Frank Force
|
|
9
|
-
*
|
|
9
|
+
*
|
|
10
10
|
* Engine Features
|
|
11
11
|
* - Object oriented system with base class engine object
|
|
12
12
|
* - Base class object handles update, physics, collision, rendering, etc
|
|
@@ -33,7 +33,7 @@ const engineName = 'LittleJS';
|
|
|
33
33
|
* @type {string}
|
|
34
34
|
* @default
|
|
35
35
|
* @memberof Engine */
|
|
36
|
-
const engineVersion = '1.
|
|
36
|
+
const engineVersion = '1.14.2';
|
|
37
37
|
|
|
38
38
|
/** Frames per second to update
|
|
39
39
|
* @type {number}
|
|
@@ -123,7 +123,7 @@ function engineAddPlugin(updateFunction, renderFunction)
|
|
|
123
123
|
* // Basic engine startup
|
|
124
124
|
* engineInit(
|
|
125
125
|
* () => { console.log('Game initialized!'); }, // gameInit
|
|
126
|
-
* () => {
|
|
126
|
+
* () => { updateGameLogic(); }, // gameUpdate
|
|
127
127
|
* () => { updateUI(); }, // gameUpdatePost
|
|
128
128
|
* () => { drawBackground(); }, // gameRender
|
|
129
129
|
* () => { drawHUD(); }, // gameRenderPost
|
|
@@ -149,7 +149,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
149
149
|
mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
|
|
150
150
|
|
|
151
151
|
// disable smoothing for pixel art
|
|
152
|
-
overlayContext.imageSmoothingEnabled =
|
|
152
|
+
overlayContext.imageSmoothingEnabled =
|
|
153
153
|
mainContext.imageSmoothingEnabled = !tilesPixelated;
|
|
154
154
|
|
|
155
155
|
// setup gl rendering if enabled
|
|
@@ -197,7 +197,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
197
197
|
deltaSmooth = frameTimeBufferMS;
|
|
198
198
|
frameTimeBufferMS = 0;
|
|
199
199
|
}
|
|
200
|
-
|
|
200
|
+
|
|
201
201
|
// update multiple frames if necessary in case of slow framerate
|
|
202
202
|
for (;frameTimeBufferMS >= 0; frameTimeBufferMS -= 1e3 / frameRate)
|
|
203
203
|
{
|
|
@@ -242,7 +242,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
242
242
|
overlayContext.textBaseline = 'top';
|
|
243
243
|
overlayContext.font = '1em monospace';
|
|
244
244
|
overlayContext.fillStyle = '#000';
|
|
245
|
-
const text = engineName + ' ' + 'v' + engineVersion + ' / '
|
|
245
|
+
const text = engineName + ' ' + 'v' + engineVersion + ' / '
|
|
246
246
|
+ drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
|
|
247
247
|
+ (glEnable ? ' GL' : ' 2D') ;
|
|
248
248
|
overlayContext.fillText(text, mainCanvas.width-3, 3);
|
|
@@ -258,13 +258,13 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
258
258
|
function updateCanvas()
|
|
259
259
|
{
|
|
260
260
|
if (headlessMode) return;
|
|
261
|
-
|
|
261
|
+
|
|
262
262
|
if (canvasFixedSize.x)
|
|
263
263
|
{
|
|
264
264
|
// clear canvas and set fixed size
|
|
265
265
|
mainCanvas.width = canvasFixedSize.x;
|
|
266
266
|
mainCanvas.height = canvasFixedSize.y;
|
|
267
|
-
|
|
267
|
+
|
|
268
268
|
// fit to window by adding space on top or bottom if necessary
|
|
269
269
|
const aspect = innerWidth / innerHeight;
|
|
270
270
|
const fixedAspect = mainCanvas.width / mainCanvas.height;
|
|
@@ -274,10 +274,10 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
274
274
|
else
|
|
275
275
|
{
|
|
276
276
|
// clear canvas and set size to same as window
|
|
277
|
-
mainCanvas.width
|
|
277
|
+
mainCanvas.width = min(innerWidth, canvasMaxSize.x);
|
|
278
278
|
mainCanvas.height = min(innerHeight, canvasMaxSize.y);
|
|
279
279
|
}
|
|
280
|
-
|
|
280
|
+
|
|
281
281
|
// clear overlay canvas and set size
|
|
282
282
|
overlayCanvas.width = mainCanvas.width;
|
|
283
283
|
overlayCanvas.height = mainCanvas.height;
|
|
@@ -296,15 +296,14 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
296
296
|
return startEngine();
|
|
297
297
|
|
|
298
298
|
// setup html
|
|
299
|
-
const styleRoot =
|
|
299
|
+
const styleRoot =
|
|
300
300
|
'margin:0;' + // fill the window
|
|
301
301
|
'overflow:hidden;' + // no scroll bars
|
|
302
302
|
'background:#000;' + // set background color
|
|
303
303
|
'user-select:none;' + // prevent hold to select
|
|
304
304
|
'-webkit-user-select:none;' + // compatibility for ios
|
|
305
|
-
(!touchInputEnable ? '' : // no touch css settings
|
|
306
305
|
'touch-action:none;' + // prevent mobile pinch to resize
|
|
307
|
-
'-webkit-touch-callout:none'
|
|
306
|
+
'-webkit-touch-callout:none';// compatibility for ios
|
|
308
307
|
rootElement.style.cssText = styleRoot;
|
|
309
308
|
drawCanvas = mainCanvas = document.createElement('canvas');
|
|
310
309
|
rootElement.appendChild(mainCanvas);
|
|
@@ -321,7 +320,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
321
320
|
rootElement.appendChild(overlayCanvas);
|
|
322
321
|
overlayContext = overlayCanvas.getContext('2d');
|
|
323
322
|
|
|
324
|
-
// set
|
|
323
|
+
// set canvases
|
|
325
324
|
const styleCanvas = 'position:absolute;'+ // allow canvases to overlap
|
|
326
325
|
'top:50%;left:50%;transform:translate(-50%,-50%)'; // center on screen
|
|
327
326
|
mainCanvas.style.cssText = overlayCanvas.style.cssText = styleCanvas;
|
|
@@ -330,17 +329,18 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
330
329
|
setCanvasPixelated(canvasPixelated);
|
|
331
330
|
setOverlayCanvasPixelated(overlayCanvasPixelated);
|
|
332
331
|
updateCanvas();
|
|
332
|
+
glPreRender();
|
|
333
333
|
|
|
334
334
|
// create offscreen canvas for image processing
|
|
335
335
|
workCanvas = new OffscreenCanvas(256, 256);
|
|
336
336
|
workContext = workCanvas.getContext('2d', { willReadFrequently: true });
|
|
337
|
-
|
|
337
|
+
|
|
338
338
|
// create promises for loading images
|
|
339
339
|
const promises = imageSources.map((src, textureIndex)=>
|
|
340
|
-
new Promise(resolve =>
|
|
340
|
+
new Promise(resolve =>
|
|
341
341
|
{
|
|
342
342
|
const image = new Image;
|
|
343
|
-
image.onerror = image.onload = ()=>
|
|
343
|
+
image.onerror = image.onload = ()=>
|
|
344
344
|
{
|
|
345
345
|
const textureInfo = new TextureInfo(image);
|
|
346
346
|
textureInfo.createWebGLTexture();
|
|
@@ -355,7 +355,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
355
355
|
if (!imageSources.length)
|
|
356
356
|
{
|
|
357
357
|
// no images to load
|
|
358
|
-
promises.push(new Promise(resolve =>
|
|
358
|
+
promises.push(new Promise(resolve =>
|
|
359
359
|
{
|
|
360
360
|
const textureInfo = new TextureInfo(new Image);
|
|
361
361
|
textureInfos[0] = textureInfo;
|
|
@@ -367,7 +367,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
367
367
|
if (showSplashScreen)
|
|
368
368
|
{
|
|
369
369
|
// draw splash screen
|
|
370
|
-
promises.push(new Promise(resolve =>
|
|
370
|
+
promises.push(new Promise(resolve =>
|
|
371
371
|
{
|
|
372
372
|
let t = 0;
|
|
373
373
|
console.log(`${engineName} Engine v${engineVersion}`);
|
|
@@ -599,7 +599,7 @@ function drawEngineSplashScreen(t)
|
|
|
599
599
|
line(36,20,60,20);
|
|
600
600
|
|
|
601
601
|
// engine front light
|
|
602
|
-
circle(60,30,4,PI,3*PI,color(3,2));
|
|
602
|
+
circle(60,30,4,PI,3*PI,color(3,2));
|
|
603
603
|
circle(60,30,4,PI,2*PI,color(3,3));
|
|
604
604
|
circle(60,30,4,PI,3*PI);
|
|
605
605
|
|
|
@@ -648,10 +648,10 @@ function drawEngineSplashScreen(t)
|
|
|
648
648
|
x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
|
|
649
649
|
X += w;
|
|
650
650
|
}
|
|
651
|
-
|
|
651
|
+
|
|
652
652
|
x.restore();
|
|
653
653
|
}
|
|
654
|
-
/**
|
|
654
|
+
/**
|
|
655
655
|
* LittleJS - Release Mode
|
|
656
656
|
* - This file is used for release builds in place of engineDebug.js
|
|
657
657
|
* - Debug functionality is disabled to reduce size and increase performance
|
|
@@ -764,7 +764,19 @@ function lerp(valueA, valueB, percent)
|
|
|
764
764
|
if (valueA >= 0 && valueA <= 1 && ((valueB < 0 || valueB > 1) && (percent < 0 || percent > 1)))
|
|
765
765
|
console.warn('lerp() parameter order changed! use lerp(start, end, p)');
|
|
766
766
|
return valueA + clamp(percent) * (valueB-valueA);
|
|
767
|
-
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/** Gets percent between percentA and percentB and linearly interpolates between lerpA and lerpB
|
|
770
|
+
* A shortcut for lerp(lerpA, lerpB, percent(value, percentA, percentB))
|
|
771
|
+
* @param {number} value
|
|
772
|
+
* @param {number} percentA
|
|
773
|
+
* @param {number} percentB
|
|
774
|
+
* @param {number} lerpA
|
|
775
|
+
* @param {number} lerpB
|
|
776
|
+
* @return {number}
|
|
777
|
+
* @memberof Utilities */
|
|
778
|
+
function percentLerp(value, percentA, percentB, lerpA, lerpB)
|
|
779
|
+
{ return lerp(lerpA, lerpB, percent(value, percentA, percentB)); }
|
|
768
780
|
|
|
769
781
|
/** Returns signed wrapped distance between the two values passed in
|
|
770
782
|
* @param {number} valueA
|
|
@@ -816,7 +828,7 @@ function smoothStep(percent) { return percent * percent * (3 - 2 * percent); }
|
|
|
816
828
|
* @memberof Utilities */
|
|
817
829
|
function isPowerOfTwo(value) { return !(value & (value - 1)); }
|
|
818
830
|
|
|
819
|
-
/** Returns the nearest power of two not less
|
|
831
|
+
/** Returns the nearest power of two not less than the value
|
|
820
832
|
* @param {number} value
|
|
821
833
|
* @return {number}
|
|
822
834
|
* @memberof Utilities */
|
|
@@ -831,8 +843,8 @@ function nearestPowerOfTwo(value) { return 2**Math.ceil(Math.log2(value)); }
|
|
|
831
843
|
* @return {boolean} - True if overlapping
|
|
832
844
|
* @memberof Utilities */
|
|
833
845
|
function isOverlapping(posA, sizeA, posB, sizeB=vec2())
|
|
834
|
-
{
|
|
835
|
-
return abs(posA.x - posB.x)*2 < sizeA.x + sizeB.x
|
|
846
|
+
{
|
|
847
|
+
return abs(posA.x - posB.x)*2 < sizeA.x + sizeB.x
|
|
836
848
|
&& abs(posA.y - posB.y)*2 < sizeA.y + sizeB.y;
|
|
837
849
|
}
|
|
838
850
|
|
|
@@ -887,7 +899,7 @@ function isIntersecting(start, end, pos, size)
|
|
|
887
899
|
function wave(frequency=1, amplitude=1, t=time, offset=0)
|
|
888
900
|
{ return amplitude/2 * (1 - Math.cos(offset + t*frequency*2*PI)); }
|
|
889
901
|
|
|
890
|
-
/** Formats seconds to mm:ss style for display purposes
|
|
902
|
+
/** Formats seconds to mm:ss style for display purposes
|
|
891
903
|
* @param {number} t - time in seconds
|
|
892
904
|
* @return {string}
|
|
893
905
|
* @memberof Utilities */
|
|
@@ -903,13 +915,12 @@ async function fetchJSON(url)
|
|
|
903
915
|
return response.json();
|
|
904
916
|
}
|
|
905
917
|
|
|
906
|
-
/**
|
|
918
|
+
/**
|
|
907
919
|
* Check if object is a valid number, not NaN or undefined, but it may be infinite
|
|
908
920
|
* @param {any} n
|
|
909
921
|
* @return {boolean}
|
|
910
|
-
* @memberof Utilities
|
|
911
|
-
|
|
912
|
-
function isNumber(n) { return typeof n == 'number' && !isNaN(n); }
|
|
922
|
+
* @memberof Utilities */
|
|
923
|
+
function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
|
|
913
924
|
|
|
914
925
|
///////////////////////////////////////////////////////////////////////////////
|
|
915
926
|
|
|
@@ -964,13 +975,13 @@ function randInCircle(radius=1, minRadius=0)
|
|
|
964
975
|
* @memberof Random */
|
|
965
976
|
function randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear=false)
|
|
966
977
|
{
|
|
967
|
-
return linear ? colorA.lerp(colorB, rand()) :
|
|
978
|
+
return linear ? colorA.lerp(colorB, rand()) :
|
|
968
979
|
new Color(rand(colorA.r,colorB.r), rand(colorA.g,colorB.g), rand(colorA.b,colorB.b), rand(colorA.a,colorB.a));
|
|
969
980
|
}
|
|
970
981
|
|
|
971
982
|
///////////////////////////////////////////////////////////////////////////////
|
|
972
983
|
|
|
973
|
-
/**
|
|
984
|
+
/**
|
|
974
985
|
* Seeded random number generator
|
|
975
986
|
* - Can be used to create a deterministic random number sequence
|
|
976
987
|
* @example
|
|
@@ -997,8 +1008,8 @@ class RandomGenerator
|
|
|
997
1008
|
float(valueA=1, valueB=0)
|
|
998
1009
|
{
|
|
999
1010
|
// xorshift algorithm
|
|
1000
|
-
this.seed ^= this.seed << 13;
|
|
1001
|
-
this.seed ^= this.seed >>> 17;
|
|
1011
|
+
this.seed ^= this.seed << 13;
|
|
1012
|
+
this.seed ^= this.seed >>> 17;
|
|
1002
1013
|
this.seed ^= this.seed << 5;
|
|
1003
1014
|
return valueB + (valueA - valueB) * ((this.seed >>> 0) / 2**32);
|
|
1004
1015
|
}
|
|
@@ -1047,16 +1058,14 @@ class RandomGenerator
|
|
|
1047
1058
|
* let a = vec2(0, 1); // vector with coordinates (0, 1)
|
|
1048
1059
|
* a = vec2(5); // set a to (5, 5)
|
|
1049
1060
|
* b = vec2(); // set b to (0, 0)
|
|
1050
|
-
* @memberof Utilities
|
|
1051
|
-
*/
|
|
1061
|
+
* @memberof Utilities */
|
|
1052
1062
|
function vec2(x=0, y) { return new Vector2(x, y === undefined ? x : y); }
|
|
1053
1063
|
|
|
1054
|
-
/**
|
|
1064
|
+
/**
|
|
1055
1065
|
* Check if object is a valid Vector2
|
|
1056
1066
|
* @param {any} v
|
|
1057
1067
|
* @return {boolean}
|
|
1058
|
-
* @memberof Utilities
|
|
1059
|
-
*/
|
|
1068
|
+
* @memberof Utilities */
|
|
1060
1069
|
function isVector2(v) { return v instanceof Vector2; }
|
|
1061
1070
|
|
|
1062
1071
|
// vector2 asserts
|
|
@@ -1065,10 +1074,10 @@ function ASSERT_NUMBER_VALID(n) { ASSERT(isNumber(n), 'Number is invalid.', n);
|
|
|
1065
1074
|
function ASSERT_VECTOR2_NORMAL(v)
|
|
1066
1075
|
{
|
|
1067
1076
|
ASSERT_VECTOR2_VALID(v);
|
|
1068
|
-
ASSERT(abs(v.lengthSquared()-1) < .01, 'Vector2 is not normal.', v);
|
|
1077
|
+
ASSERT(abs(v.lengthSquared()-1) < .01, 'Vector2 is not normal.', v);
|
|
1069
1078
|
}
|
|
1070
1079
|
|
|
1071
|
-
/**
|
|
1080
|
+
/**
|
|
1072
1081
|
* 2D Vector object with vector math library
|
|
1073
1082
|
* - Functions do not change this so they can be chained together
|
|
1074
1083
|
* @example
|
|
@@ -1192,7 +1201,7 @@ class Vector2
|
|
|
1192
1201
|
* @param {number} [angle]
|
|
1193
1202
|
* @param {number} [length]
|
|
1194
1203
|
* @return {Vector2} */
|
|
1195
|
-
setAngle(angle=0, length=1)
|
|
1204
|
+
setAngle(angle=0, length=1)
|
|
1196
1205
|
{
|
|
1197
1206
|
ASSERT_NUMBER_VALID(angle);
|
|
1198
1207
|
ASSERT_NUMBER_VALID(length);
|
|
@@ -1207,7 +1216,7 @@ class Vector2
|
|
|
1207
1216
|
rotate(angle)
|
|
1208
1217
|
{
|
|
1209
1218
|
ASSERT_NUMBER_VALID(angle);
|
|
1210
|
-
const c = Math.cos(-angle), s = Math.sin(-angle);
|
|
1219
|
+
const c = Math.cos(-angle), s = Math.sin(-angle);
|
|
1211
1220
|
return new Vector2(this.x*c - this.y*s, this.x*s + this.y*c);
|
|
1212
1221
|
}
|
|
1213
1222
|
|
|
@@ -1219,9 +1228,9 @@ class Vector2
|
|
|
1219
1228
|
ASSERT_NUMBER_VALID(direction);
|
|
1220
1229
|
ASSERT_NUMBER_VALID(length);
|
|
1221
1230
|
direction = mod(direction, 4);
|
|
1222
|
-
ASSERT(direction
|
|
1231
|
+
ASSERT(direction===0 || direction===1 || direction===2 || direction===3,
|
|
1223
1232
|
'Vector2.setDirection() direction must be an integer between 0 and 3.');
|
|
1224
|
-
return vec2(direction%2 ? direction-1 ? -length : length : 0,
|
|
1233
|
+
return vec2(direction%2 ? direction-1 ? -length : length : 0,
|
|
1225
1234
|
direction%2 ? 0 : direction ? -length : length);
|
|
1226
1235
|
}
|
|
1227
1236
|
|
|
@@ -1273,7 +1282,7 @@ class Vector2
|
|
|
1273
1282
|
/** Returns this vector expressed as a string
|
|
1274
1283
|
* @param {number} digits - precision to display
|
|
1275
1284
|
* @return {string} */
|
|
1276
|
-
toString(digits=3)
|
|
1285
|
+
toString(digits=3)
|
|
1277
1286
|
{
|
|
1278
1287
|
ASSERT_NUMBER_VALID(digits);
|
|
1279
1288
|
if (debug)
|
|
@@ -1292,7 +1301,7 @@ class Vector2
|
|
|
1292
1301
|
|
|
1293
1302
|
///////////////////////////////////////////////////////////////////////////////
|
|
1294
1303
|
|
|
1295
|
-
/**
|
|
1304
|
+
/**
|
|
1296
1305
|
* Create a color object with RGBA values, white by default
|
|
1297
1306
|
* @param {number} [r=1] - red
|
|
1298
1307
|
* @param {number} [g=1] - green
|
|
@@ -1303,29 +1312,27 @@ class Vector2
|
|
|
1303
1312
|
*/
|
|
1304
1313
|
function rgb(r, g, b, a) { return new Color(r, g, b, a); }
|
|
1305
1314
|
|
|
1306
|
-
/**
|
|
1315
|
+
/**
|
|
1307
1316
|
* Create a color object with HSLA values, white by default
|
|
1308
1317
|
* @param {number} [h=0] - hue
|
|
1309
1318
|
* @param {number} [s=0] - saturation
|
|
1310
1319
|
* @param {number} [l=1] - lightness
|
|
1311
1320
|
* @param {number} [a=1] - alpha
|
|
1312
1321
|
* @return {Color}
|
|
1313
|
-
* @memberof Utilities
|
|
1314
|
-
*/
|
|
1322
|
+
* @memberof Utilities */
|
|
1315
1323
|
function hsl(h, s, l, a) { return new Color().setHSLA(h, s, l, a); }
|
|
1316
1324
|
|
|
1317
|
-
/**
|
|
1325
|
+
/**
|
|
1318
1326
|
* Check if object is a valid Color
|
|
1319
1327
|
* @param {any} c
|
|
1320
1328
|
* @return {boolean}
|
|
1321
|
-
* @memberof Utilities
|
|
1322
|
-
*/
|
|
1329
|
+
* @memberof Utilities */
|
|
1323
1330
|
function isColor(c) { return c instanceof Color; }
|
|
1324
1331
|
|
|
1325
1332
|
// color asserts
|
|
1326
1333
|
function ASSERT_COLOR_VALID(c) { ASSERT(isColor(c) && c.isValid(), 'Color is invalid.', c); }
|
|
1327
1334
|
|
|
1328
|
-
/**
|
|
1335
|
+
/**
|
|
1329
1336
|
* Color object (red, green, blue, alpha) with some helpful functions
|
|
1330
1337
|
* @example
|
|
1331
1338
|
* let a = new Color; // white
|
|
@@ -1397,7 +1404,7 @@ class Color
|
|
|
1397
1404
|
* @param {number} scale
|
|
1398
1405
|
* @param {number} [alphaScale=scale]
|
|
1399
1406
|
* @return {Color} */
|
|
1400
|
-
scale(scale, alphaScale=scale)
|
|
1407
|
+
scale(scale, alphaScale=scale)
|
|
1401
1408
|
{ return new Color(this.r*scale, this.g*scale, this.b*scale, this.a*alphaScale); }
|
|
1402
1409
|
|
|
1403
1410
|
/** Returns a copy of this color clamped to the valid range between 0 and 1
|
|
@@ -1414,9 +1421,9 @@ class Color
|
|
|
1414
1421
|
ASSERT_NUMBER_VALID(percent);
|
|
1415
1422
|
const p = clamp(percent);
|
|
1416
1423
|
return new Color(
|
|
1417
|
-
c.r*p + this.r*(1-p),
|
|
1418
|
-
c.g*p + this.g*(1-p),
|
|
1419
|
-
c.b*p + this.b*(1-p),
|
|
1424
|
+
c.r*p + this.r*(1-p),
|
|
1425
|
+
c.g*p + this.g*(1-p),
|
|
1426
|
+
c.b*p + this.b*(1-p),
|
|
1420
1427
|
c.a*p + this.a*(1-p));
|
|
1421
1428
|
}
|
|
1422
1429
|
|
|
@@ -1456,15 +1463,15 @@ class Color
|
|
|
1456
1463
|
const minC = min(r, g, b);
|
|
1457
1464
|
const l = (maxC + minC) / 2;
|
|
1458
1465
|
let h = 0, s = 0;
|
|
1459
|
-
if (maxC
|
|
1466
|
+
if (maxC !== minC)
|
|
1460
1467
|
{
|
|
1461
1468
|
let d = maxC - minC;
|
|
1462
1469
|
s = l > .5 ? d / (2 - maxC - minC) : d / (maxC + minC);
|
|
1463
|
-
if (r
|
|
1470
|
+
if (r === maxC)
|
|
1464
1471
|
h = (g - b) / d + (g < b ? 6 : 0);
|
|
1465
|
-
else if (g
|
|
1472
|
+
else if (g === maxC)
|
|
1466
1473
|
h = (b - r) / d + 2;
|
|
1467
|
-
else if (b
|
|
1474
|
+
else if (b === maxC)
|
|
1468
1475
|
h = (r - g) / d + 4;
|
|
1469
1476
|
}
|
|
1470
1477
|
return [h / 6, s, l, a];
|
|
@@ -1474,7 +1481,7 @@ class Color
|
|
|
1474
1481
|
* @param {number} [amount]
|
|
1475
1482
|
* @param {number} [alphaAmount]
|
|
1476
1483
|
* @return {Color} */
|
|
1477
|
-
mutate(amount=.05, alphaAmount=0)
|
|
1484
|
+
mutate(amount=.05, alphaAmount=0)
|
|
1478
1485
|
{
|
|
1479
1486
|
ASSERT_NUMBER_VALID(amount);
|
|
1480
1487
|
ASSERT_NUMBER_VALID(alphaAmount);
|
|
@@ -1490,47 +1497,47 @@ class Color
|
|
|
1490
1497
|
/** Returns this color expressed as a hex color code
|
|
1491
1498
|
* @param {boolean} [useAlpha] - if alpha should be included in result
|
|
1492
1499
|
* @return {string} */
|
|
1493
|
-
toString(useAlpha = true)
|
|
1500
|
+
toString(useAlpha = true)
|
|
1494
1501
|
{
|
|
1495
|
-
ASSERT(typeof useAlpha
|
|
1502
|
+
ASSERT(typeof useAlpha === 'boolean', 'Use alpha boolean is invalid.', useAlpha);
|
|
1496
1503
|
if (debug && !this.isValid())
|
|
1497
1504
|
return `#000`;
|
|
1498
1505
|
const toHex = (c)=> ((c=clamp(c)*255|0)<16 ? '0' : '') + c.toString(16);
|
|
1499
1506
|
return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
|
|
1500
1507
|
}
|
|
1501
|
-
|
|
1508
|
+
|
|
1502
1509
|
/** Set this color from a hex code
|
|
1503
1510
|
* @param {string} hex - html hex code
|
|
1504
1511
|
* @return {Color} */
|
|
1505
1512
|
setHex(hex)
|
|
1506
1513
|
{
|
|
1507
|
-
ASSERT(typeof hex
|
|
1514
|
+
ASSERT(typeof hex === 'string' && hex[0] === '#', 'Color hex code must be a string starting with #');
|
|
1508
1515
|
ASSERT([4,5,7,9].includes(hex.length), 'Invalid hex');
|
|
1509
1516
|
|
|
1510
1517
|
if (hex.length < 6)
|
|
1511
1518
|
{
|
|
1512
1519
|
const fromHex = (c)=> clamp(parseInt(hex[c],16)/15);
|
|
1513
1520
|
this.r = fromHex(1);
|
|
1514
|
-
this.g = fromHex(2)
|
|
1521
|
+
this.g = fromHex(2);
|
|
1515
1522
|
this.b = fromHex(3);
|
|
1516
|
-
this.a = hex.length
|
|
1523
|
+
this.a = hex.length === 5 ? fromHex(4) : 1;
|
|
1517
1524
|
}
|
|
1518
1525
|
else
|
|
1519
1526
|
{
|
|
1520
1527
|
const fromHex = (c)=> clamp(parseInt(hex.slice(c,c+2),16)/255);
|
|
1521
1528
|
this.r = fromHex(1);
|
|
1522
|
-
this.g = fromHex(3)
|
|
1529
|
+
this.g = fromHex(3);
|
|
1523
1530
|
this.b = fromHex(5);
|
|
1524
|
-
this.a = hex.length
|
|
1531
|
+
this.a = hex.length === 9 ? fromHex(7) : 1;
|
|
1525
1532
|
}
|
|
1526
1533
|
|
|
1527
1534
|
ASSERT_COLOR_VALID(this);
|
|
1528
1535
|
return this;
|
|
1529
1536
|
}
|
|
1530
|
-
|
|
1537
|
+
|
|
1531
1538
|
/** Returns this color expressed as 32 bit RGBA value
|
|
1532
1539
|
* @return {number} */
|
|
1533
|
-
rgbaInt()
|
|
1540
|
+
rgbaInt()
|
|
1534
1541
|
{
|
|
1535
1542
|
const r = clamp(this.r)*255|0;
|
|
1536
1543
|
const g = clamp(this.g)*255<<8;
|
|
@@ -1551,7 +1558,7 @@ class Color
|
|
|
1551
1558
|
/** Color - White #ffffff
|
|
1552
1559
|
* @type {Color}
|
|
1553
1560
|
* @memberof Utilities */
|
|
1554
|
-
const WHITE = rgb();
|
|
1561
|
+
const WHITE = rgb();
|
|
1555
1562
|
|
|
1556
1563
|
/** Color - Clear White #ffffff with 0 alpha
|
|
1557
1564
|
* @type {Color}
|
|
@@ -1666,11 +1673,11 @@ class Timer
|
|
|
1666
1673
|
/** Get percentage elapsed based on time it was set to, returns 0 if not set
|
|
1667
1674
|
* @return {number} */
|
|
1668
1675
|
getPercent() { return this.isSet()? 1-percent(this.time - time, 0, this.setTime) : 0; }
|
|
1669
|
-
|
|
1676
|
+
|
|
1670
1677
|
/** Returns this timer expressed as a string
|
|
1671
1678
|
* @return {string} */
|
|
1672
1679
|
toString() { if (debug) { return this.isSet() ? Math.abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; }}
|
|
1673
|
-
|
|
1680
|
+
|
|
1674
1681
|
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
1675
1682
|
* @return {number} */
|
|
1676
1683
|
valueOf() { return this.get(); }
|
|
@@ -1706,7 +1713,7 @@ let cameraScale = 32;
|
|
|
1706
1713
|
// Display settings
|
|
1707
1714
|
|
|
1708
1715
|
/** Enable applying color to tiles when using canvas2d
|
|
1709
|
-
* - This is slower but should be the same as
|
|
1716
|
+
* - This is slower but should be the same as WebGL rendering
|
|
1710
1717
|
* @type {boolean}
|
|
1711
1718
|
* @default
|
|
1712
1719
|
* @memberof Settings */
|
|
@@ -1752,14 +1759,13 @@ let tilesPixelated = true;
|
|
|
1752
1759
|
* @memberof Settings */
|
|
1753
1760
|
let fontDefault = 'arial';
|
|
1754
1761
|
|
|
1755
|
-
/** Enable to show the LittleJS splash screen
|
|
1762
|
+
/** Enable to show the LittleJS splash screen on startup
|
|
1756
1763
|
* @type {boolean}
|
|
1757
1764
|
* @default
|
|
1758
1765
|
* @memberof Settings */
|
|
1759
1766
|
let showSplashScreen = false;
|
|
1760
1767
|
|
|
1761
1768
|
/** Disables all rendering, audio, and input for servers
|
|
1762
|
-
* - Must be set before startup to take effect
|
|
1763
1769
|
* @type {boolean}
|
|
1764
1770
|
* @default
|
|
1765
1771
|
* @memberof Settings */
|
|
@@ -1768,13 +1774,18 @@ let headlessMode = false;
|
|
|
1768
1774
|
///////////////////////////////////////////////////////////////////////////////
|
|
1769
1775
|
// WebGL settings
|
|
1770
1776
|
|
|
1771
|
-
/** Enable
|
|
1772
|
-
* - Must be set before startup to take effect
|
|
1777
|
+
/** Enable WebGL accelerated rendering
|
|
1773
1778
|
* @type {boolean}
|
|
1774
1779
|
* @default
|
|
1775
1780
|
* @memberof Settings */
|
|
1776
1781
|
let glEnable = true;
|
|
1777
1782
|
|
|
1783
|
+
/** How many sided poly to use when drawing circles and ellipses with WebGL
|
|
1784
|
+
* @type {number}
|
|
1785
|
+
* @default
|
|
1786
|
+
* @memberof Settings */
|
|
1787
|
+
let glCircleSides = 32;
|
|
1788
|
+
|
|
1778
1789
|
///////////////////////////////////////////////////////////////////////////////
|
|
1779
1790
|
// Tile sheet settings
|
|
1780
1791
|
|
|
@@ -1856,13 +1867,13 @@ let particleEmitRateScale = 1;
|
|
|
1856
1867
|
* @memberof Settings */
|
|
1857
1868
|
let gamepadsEnable = true;
|
|
1858
1869
|
|
|
1859
|
-
/** If true, the dpad input is also routed to the left analog stick (for better
|
|
1870
|
+
/** If true, the dpad input is also routed to the left analog stick (for better accessibility)
|
|
1860
1871
|
* @type {boolean}
|
|
1861
1872
|
* @default
|
|
1862
1873
|
* @memberof Settings */
|
|
1863
1874
|
let gamepadDirectionEmulateStick = true;
|
|
1864
1875
|
|
|
1865
|
-
/** If true the WASD keys are also routed to the direction keys (for better
|
|
1876
|
+
/** If true the WASD keys are also routed to the direction keys (for better accessibility)
|
|
1866
1877
|
* @type {boolean}
|
|
1867
1878
|
* @default
|
|
1868
1879
|
* @memberof Settings */
|
|
@@ -1870,7 +1881,6 @@ let inputWASDEmulateDirection = true;
|
|
|
1870
1881
|
|
|
1871
1882
|
/** True if touch input is enabled for mobile devices
|
|
1872
1883
|
* - Touch events will be routed to mouse events
|
|
1873
|
-
* - Must be set before startup to take effect
|
|
1874
1884
|
* @type {boolean}
|
|
1875
1885
|
* @default
|
|
1876
1886
|
* @memberof Settings */
|
|
@@ -1878,7 +1888,6 @@ let touchInputEnable = true;
|
|
|
1878
1888
|
|
|
1879
1889
|
/** True if touch gamepad should appear on mobile devices
|
|
1880
1890
|
* - Supports left analog stick, 4 face buttons and start button (button 9)
|
|
1881
|
-
* - Must be set before startup to take effect
|
|
1882
1891
|
* @type {boolean}
|
|
1883
1892
|
* @default
|
|
1884
1893
|
* @memberof Settings */
|
|
@@ -1981,7 +1990,7 @@ function setCameraAngle(angle) { cameraAngle = angle; }
|
|
|
1981
1990
|
function setCameraScale(scale) { cameraScale = scale; }
|
|
1982
1991
|
|
|
1983
1992
|
/** Set if tiles should be colorized when using canvas2d
|
|
1984
|
-
* This can be slower but results should look nearly identical to
|
|
1993
|
+
* This can be slower but results should look nearly identical to WebGL rendering
|
|
1985
1994
|
* It can be enabled/disabled at any time
|
|
1986
1995
|
* Optimized for performance, and will use faster method if color is white or untextured
|
|
1987
1996
|
* @param {boolean} colorTiles
|
|
@@ -2017,8 +2026,8 @@ function setCanvasPixelated(pixelated)
|
|
|
2017
2026
|
* @param {boolean} pixelated
|
|
2018
2027
|
* @memberof Settings */
|
|
2019
2028
|
function setOverlayCanvasPixelated(pixelated)
|
|
2020
|
-
{
|
|
2021
|
-
overlayCanvasPixelated = pixelated;
|
|
2029
|
+
{
|
|
2030
|
+
overlayCanvasPixelated = pixelated;
|
|
2022
2031
|
if (overlayCanvas)
|
|
2023
2032
|
overlayCanvas.style.imageRendering = pixelated ? 'pixelated' : '';
|
|
2024
2033
|
}
|
|
@@ -2033,7 +2042,7 @@ function setTilesPixelated(pixelated) { tilesPixelated = pixelated; }
|
|
|
2033
2042
|
* @memberof Settings */
|
|
2034
2043
|
function setFontDefault(font) { fontDefault = font; }
|
|
2035
2044
|
|
|
2036
|
-
/** Set if the LittleJS splash screen be shown on startup
|
|
2045
|
+
/** Set if the LittleJS splash screen should be shown on startup
|
|
2037
2046
|
* @param {boolean} show
|
|
2038
2047
|
* @memberof Settings */
|
|
2039
2048
|
function setShowSplashScreen(show) { showSplashScreen = show; }
|
|
@@ -2043,16 +2052,21 @@ function setShowSplashScreen(show) { showSplashScreen = show; }
|
|
|
2043
2052
|
* @memberof Settings */
|
|
2044
2053
|
function setHeadlessMode(headless) { headlessMode = headless; }
|
|
2045
2054
|
|
|
2046
|
-
/** Set if
|
|
2055
|
+
/** Set if WebGL rendering is enabled
|
|
2047
2056
|
* @param {boolean} enable
|
|
2048
2057
|
* @memberof Settings */
|
|
2049
2058
|
function setGLEnable(enable)
|
|
2050
2059
|
{
|
|
2051
2060
|
glEnable = enable;
|
|
2052
|
-
if (glCanvas) // hide glCanvas if
|
|
2061
|
+
if (glCanvas) // hide glCanvas if WebGL is disabled
|
|
2053
2062
|
glCanvas.style.visibility = enable ? 'visible' : 'hidden';
|
|
2054
2063
|
}
|
|
2055
2064
|
|
|
2065
|
+
/** Set how many sided polygons to use when drawing circles and elipses with WebGL
|
|
2066
|
+
* @param {number} sides
|
|
2067
|
+
* @memberof Settings */
|
|
2068
|
+
function setGLCircleSides(sides) { glCircleSides = sides; }
|
|
2069
|
+
|
|
2056
2070
|
/** Set default size of tiles in pixels
|
|
2057
2071
|
* @param {Vector2} size
|
|
2058
2072
|
* @memberof Settings */
|
|
@@ -2083,7 +2097,7 @@ function setObjectDefaultDamping(damp) { objectDefaultDamping = damp; }
|
|
|
2083
2097
|
* @memberof Settings */
|
|
2084
2098
|
function setObjectDefaultAngleDamping(damp) { objectDefaultAngleDamping = damp; }
|
|
2085
2099
|
|
|
2086
|
-
/** Set how much to bounce when a collision
|
|
2100
|
+
/** Set how much to bounce when a collision occurs
|
|
2087
2101
|
* @param {number} restitution
|
|
2088
2102
|
* @memberof Settings */
|
|
2089
2103
|
function setObjectDefaultRestitution(restitution) { objectDefaultRestitution = restitution; }
|
|
@@ -2207,11 +2221,11 @@ function setShowWatermark(show) { showWatermark = show; }
|
|
|
2207
2221
|
* @param {string} key
|
|
2208
2222
|
* @memberof Debug */
|
|
2209
2223
|
function setDebugKey(key) { debugKey = key; }
|
|
2210
|
-
/**
|
|
2224
|
+
/**
|
|
2211
2225
|
* LittleJS Object System
|
|
2212
2226
|
*/
|
|
2213
2227
|
|
|
2214
|
-
/**
|
|
2228
|
+
/**
|
|
2215
2229
|
* LittleJS Object Base Object Class
|
|
2216
2230
|
* - Top level object class used by the engine
|
|
2217
2231
|
* - Automatically adds self to object list
|
|
@@ -2234,7 +2248,7 @@ function setDebugKey(key) { debugKey = key; }
|
|
|
2234
2248
|
* @example
|
|
2235
2249
|
* // create an engine object, normally you would first extend the class with your own
|
|
2236
2250
|
* const pos = vec2(2,3);
|
|
2237
|
-
* const object = new EngineObject(pos);
|
|
2251
|
+
* const object = new EngineObject(pos);
|
|
2238
2252
|
*/
|
|
2239
2253
|
class EngineObject
|
|
2240
2254
|
{
|
|
@@ -2252,9 +2266,9 @@ class EngineObject
|
|
|
2252
2266
|
ASSERT(isVector2(pos) && pos.isValid(), 'object pos should be a vec2');
|
|
2253
2267
|
ASSERT(isVector2(size) && size.isValid(), 'object size should be a vec2');
|
|
2254
2268
|
ASSERT(!tileInfo || tileInfo instanceof TileInfo, 'object tileInfo should be a TileInfo or undefined');
|
|
2255
|
-
ASSERT(typeof angle
|
|
2269
|
+
ASSERT(typeof angle === 'number' && isFinite(angle), 'object angle should be a number');
|
|
2256
2270
|
ASSERT(isColor(color) && color.isValid(), 'object color should be a valid rgba color');
|
|
2257
|
-
ASSERT(typeof renderOrder
|
|
2271
|
+
ASSERT(typeof renderOrder === 'number', 'object renderOrder should be a number');
|
|
2258
2272
|
|
|
2259
2273
|
/** @property {Vector2} - World space position of the object */
|
|
2260
2274
|
this.pos = pos.copy();
|
|
@@ -2322,7 +2336,7 @@ class EngineObject
|
|
|
2322
2336
|
// add to list of objects
|
|
2323
2337
|
engineObjects.push(this);
|
|
2324
2338
|
}
|
|
2325
|
-
|
|
2339
|
+
|
|
2326
2340
|
/** Update the object transform, called automatically by engine even when paused */
|
|
2327
2341
|
updateTransforms()
|
|
2328
2342
|
{
|
|
@@ -2391,7 +2405,7 @@ class EngineObject
|
|
|
2391
2405
|
for (const o of engineObjectsCollide)
|
|
2392
2406
|
{
|
|
2393
2407
|
// non solid objects don't collide with each other
|
|
2394
|
-
if (!this.isSolid && !o.isSolid || o.destroyed || o.parent || o
|
|
2408
|
+
if (!this.isSolid && !o.isSolid || o.destroyed || o.parent || o === this)
|
|
2395
2409
|
continue;
|
|
2396
2410
|
|
|
2397
2411
|
// check collision
|
|
@@ -2414,7 +2428,7 @@ class EngineObject
|
|
|
2414
2428
|
this.velocity = this.velocity.add(velocity);
|
|
2415
2429
|
if (o.mass) // push away if not fixed
|
|
2416
2430
|
o.velocity = o.velocity.subtract(velocity);
|
|
2417
|
-
|
|
2431
|
+
|
|
2418
2432
|
debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f00');
|
|
2419
2433
|
continue;
|
|
2420
2434
|
}
|
|
@@ -2425,7 +2439,7 @@ class EngineObject
|
|
|
2425
2439
|
const isBlockedX = abs(oldPos.y - o.pos.y)*2 < sizeBoth.y;
|
|
2426
2440
|
const isBlockedY = abs(oldPos.x - o.pos.x)*2 < sizeBoth.x;
|
|
2427
2441
|
const restitution = max(this.restitution, o.restitution);
|
|
2428
|
-
|
|
2442
|
+
|
|
2429
2443
|
if (smallStepUp || isBlockedY || !isBlockedX) // resolve y collision
|
|
2430
2444
|
{
|
|
2431
2445
|
// push outside object collision
|
|
@@ -2513,7 +2527,7 @@ class EngineObject
|
|
|
2513
2527
|
{
|
|
2514
2528
|
// move to previous position
|
|
2515
2529
|
this.pos.y = oldPos.y;
|
|
2516
|
-
this.groundObject = undefined;
|
|
2530
|
+
this.groundObject = undefined;
|
|
2517
2531
|
}
|
|
2518
2532
|
}
|
|
2519
2533
|
if (blockedLayerX)
|
|
@@ -2527,20 +2541,20 @@ class EngineObject
|
|
|
2527
2541
|
}
|
|
2528
2542
|
}
|
|
2529
2543
|
}
|
|
2530
|
-
|
|
2544
|
+
|
|
2531
2545
|
/** Render the object, draws a tile by default, automatically called each frame, sorted by renderOrder */
|
|
2532
2546
|
render()
|
|
2533
2547
|
{
|
|
2534
2548
|
// default object render
|
|
2535
2549
|
drawTile(this.pos, this.drawSize || this.size, this.tileInfo, this.color, this.angle, this.mirror, this.additiveColor);
|
|
2536
2550
|
}
|
|
2537
|
-
|
|
2551
|
+
|
|
2538
2552
|
/** Destroy this object, destroy its children, detach it's parent, and mark it for removal */
|
|
2539
2553
|
destroy()
|
|
2540
|
-
{
|
|
2554
|
+
{
|
|
2541
2555
|
if (this.destroyed)
|
|
2542
2556
|
return;
|
|
2543
|
-
|
|
2557
|
+
|
|
2544
2558
|
// disconnect from parent and destroy children
|
|
2545
2559
|
this.destroyed = 1;
|
|
2546
2560
|
this.parent && this.parent.removeChild(this);
|
|
@@ -2566,7 +2580,7 @@ class EngineObject
|
|
|
2566
2580
|
/** Convert from world space to local space for a vector (rotation only)
|
|
2567
2581
|
* @param {Vector2} vec - world space vector */
|
|
2568
2582
|
worldToLocalVector(vec) { return vec.rotate(-this.angle); }
|
|
2569
|
-
|
|
2583
|
+
|
|
2570
2584
|
/** Called to check if a tile collision should be resolved
|
|
2571
2585
|
* @param {number} tileData - the value of the tile at the position
|
|
2572
2586
|
* @param {Vector2} pos - tile where the collision occurred
|
|
@@ -2585,16 +2599,19 @@ class EngineObject
|
|
|
2585
2599
|
|
|
2586
2600
|
/** Apply acceleration to this object (adjust velocity, not affected by mass)
|
|
2587
2601
|
* @param {Vector2} acceleration */
|
|
2588
|
-
applyAcceleration(acceleration)
|
|
2602
|
+
applyAcceleration(acceleration)
|
|
2603
|
+
{ if (this.mass) this.velocity = this.velocity.add(acceleration); }
|
|
2589
2604
|
|
|
2590
|
-
/** Apply angular acceleration to this object
|
|
2605
|
+
/** Apply angular acceleration to this object
|
|
2591
2606
|
* @param {number} acceleration */
|
|
2592
|
-
applyAngularAcceleration(acceleration)
|
|
2607
|
+
applyAngularAcceleration(acceleration)
|
|
2608
|
+
{ if (this.mass) this.angleVelocity += acceleration; }
|
|
2593
2609
|
|
|
2594
2610
|
/** Apply force to this object (adjust velocity, affected by mass)
|
|
2595
2611
|
* @param {Vector2} force */
|
|
2596
|
-
applyForce(force)
|
|
2597
|
-
|
|
2612
|
+
applyForce(force)
|
|
2613
|
+
{ if (this.mass) this.applyAcceleration(force.scale(1/this.mass)); }
|
|
2614
|
+
|
|
2598
2615
|
/** Get the direction of the mirror
|
|
2599
2616
|
* @return {number} -1 if this.mirror is true, or 1 if not mirrored */
|
|
2600
2617
|
getMirrorSign() { return this.mirror ? -1 : 1; }
|
|
@@ -2616,7 +2633,7 @@ class EngineObject
|
|
|
2616
2633
|
* @param {EngineObject} child */
|
|
2617
2634
|
removeChild(child)
|
|
2618
2635
|
{
|
|
2619
|
-
ASSERT(child.parent
|
|
2636
|
+
ASSERT(child.parent === this && this.children.includes(child));
|
|
2620
2637
|
this.children.splice(this.children.indexOf(child), 1);
|
|
2621
2638
|
child.parent = 0;
|
|
2622
2639
|
}
|
|
@@ -2662,7 +2679,7 @@ class EngineObject
|
|
|
2662
2679
|
{
|
|
2663
2680
|
if (!debug)
|
|
2664
2681
|
return;
|
|
2665
|
-
|
|
2682
|
+
|
|
2666
2683
|
// show object info for debugging
|
|
2667
2684
|
const size = vec2(max(this.size.x, .2), max(this.size.y, .2));
|
|
2668
2685
|
const color = rgb(this.collideTiles?1:0, this.collideSolidObjects?1:0, this.isSolid?1:0, .5);
|
|
@@ -2672,24 +2689,24 @@ class EngineObject
|
|
|
2672
2689
|
this.parent && drawLine(this.pos, this.parent.pos, .1, rgb(1,1,1,.5));
|
|
2673
2690
|
}
|
|
2674
2691
|
}
|
|
2675
|
-
/**
|
|
2692
|
+
/**
|
|
2676
2693
|
* LittleJS Drawing System
|
|
2677
2694
|
* - Hybrid system with both Canvas2D and WebGL available
|
|
2678
2695
|
* - Super fast tile sheet rendering with WebGL
|
|
2679
2696
|
* - Can apply rotation, mirror, color and additive color
|
|
2680
2697
|
* - Font rendering system with built in engine font
|
|
2681
2698
|
* - Many useful utility functions
|
|
2682
|
-
*
|
|
2699
|
+
*
|
|
2683
2700
|
* LittleJS uses a hybrid rendering solution with the best of both Canvas2D and WebGL.
|
|
2684
2701
|
* There are 3 canvas/contexts available to draw to...
|
|
2685
2702
|
* mainCanvas - 2D background canvas, non WebGL stuff like tile layers are drawn here.
|
|
2686
2703
|
* glCanvas - Used by the accelerated WebGL batch rendering system.
|
|
2687
2704
|
* overlayCanvas - Another 2D canvas that appears on top of the other 2 canvases.
|
|
2688
|
-
*
|
|
2705
|
+
*
|
|
2689
2706
|
* The WebGL rendering system is very fast with some caveats...
|
|
2690
2707
|
* - Switching blend modes (additive) or textures causes another draw call which is expensive in excess
|
|
2691
2708
|
* - Group additive rendering together using renderOrder to mitigate this issue
|
|
2692
|
-
*
|
|
2709
|
+
*
|
|
2693
2710
|
* The LittleJS rendering solution is intentionally simple, feel free to adjust it for your needs!
|
|
2694
2711
|
* @namespace Draw
|
|
2695
2712
|
*/
|
|
@@ -2734,7 +2751,7 @@ let workCanvas;
|
|
|
2734
2751
|
* @memberof Draw */
|
|
2735
2752
|
let workContext;
|
|
2736
2753
|
|
|
2737
|
-
/** The size of the main canvas (and other secondary canvases)
|
|
2754
|
+
/** The size of the main canvas (and other secondary canvases)
|
|
2738
2755
|
* @type {Vector2}
|
|
2739
2756
|
* @memberof Draw */
|
|
2740
2757
|
let mainCanvasSize = vec2();
|
|
@@ -2749,7 +2766,7 @@ let drawCount;
|
|
|
2749
2766
|
|
|
2750
2767
|
///////////////////////////////////////////////////////////////////////////////
|
|
2751
2768
|
|
|
2752
|
-
/**
|
|
2769
|
+
/**
|
|
2753
2770
|
* Create a tile info object using a grid based system
|
|
2754
2771
|
* - This can take vecs or floats for easier use and conversion
|
|
2755
2772
|
* - If an index is passed in, the tile size and index will determine the position
|
|
@@ -2763,15 +2780,14 @@ let drawCount;
|
|
|
2763
2780
|
* tile(5, 8) // a tile at index 5 using a tile size of 8
|
|
2764
2781
|
* tile(1, 16, 3) // a tile at index 1 of size 16 on texture 3
|
|
2765
2782
|
* tile(vec2(4,8), vec2(30,10)) // a tile at index (4,8) with a size of (30,10)
|
|
2766
|
-
* @memberof Draw
|
|
2767
|
-
*/
|
|
2783
|
+
* @memberof Draw */
|
|
2768
2784
|
function tile(pos=new Vector2, size=tileSizeDefault, textureIndex=0, padding=0)
|
|
2769
2785
|
{
|
|
2770
2786
|
if (headlessMode)
|
|
2771
2787
|
return new TileInfo;
|
|
2772
2788
|
|
|
2773
2789
|
// if size is a number, make it a vector
|
|
2774
|
-
if (typeof size
|
|
2790
|
+
if (typeof size === 'number')
|
|
2775
2791
|
{
|
|
2776
2792
|
ASSERT(size > 0);
|
|
2777
2793
|
size = new Vector2(size, size);
|
|
@@ -2780,24 +2796,24 @@ function tile(pos=new Vector2, size=tileSizeDefault, textureIndex=0, padding=0)
|
|
|
2780
2796
|
// create tile info object
|
|
2781
2797
|
const tileInfo = new TileInfo(new Vector2, size, textureIndex, padding);
|
|
2782
2798
|
|
|
2783
|
-
//
|
|
2799
|
+
// get the position of the tile
|
|
2784
2800
|
const textureInfo = textureInfos[textureIndex];
|
|
2785
2801
|
ASSERT(!!textureInfo, 'Texture not loaded');
|
|
2786
2802
|
const sizePaddedX = size.x + padding*2;
|
|
2787
2803
|
const sizePaddedY = size.y + padding*2;
|
|
2788
|
-
if (typeof pos
|
|
2804
|
+
if (typeof pos === 'number')
|
|
2789
2805
|
{
|
|
2790
2806
|
const cols = textureInfo.size.x / sizePaddedX |0;
|
|
2791
|
-
ASSERT(cols>0, 'Tile size is too big for texture');
|
|
2807
|
+
ASSERT(cols > 0, 'Tile size is too big for texture');
|
|
2792
2808
|
const posX = pos % cols, posY = (pos / cols) |0;
|
|
2793
2809
|
tileInfo.pos.set(posX*sizePaddedX+padding, posY*sizePaddedY+padding);
|
|
2794
2810
|
}
|
|
2795
2811
|
else
|
|
2796
2812
|
tileInfo.pos.set(pos.x*sizePaddedX+padding, pos.y*sizePaddedY+padding);
|
|
2797
|
-
return tileInfo;
|
|
2813
|
+
return tileInfo;
|
|
2798
2814
|
}
|
|
2799
2815
|
|
|
2800
|
-
/**
|
|
2816
|
+
/**
|
|
2801
2817
|
* Tile Info - Stores info about how to draw a tile
|
|
2802
2818
|
*/
|
|
2803
2819
|
class TileInfo
|
|
@@ -2835,14 +2851,14 @@ class TileInfo
|
|
|
2835
2851
|
*/
|
|
2836
2852
|
frame(frame)
|
|
2837
2853
|
{
|
|
2838
|
-
ASSERT(typeof frame
|
|
2854
|
+
ASSERT(typeof frame === 'number');
|
|
2839
2855
|
return this.offset(new Vector2(frame*(this.size.x+this.padding*2), 0));
|
|
2840
2856
|
}
|
|
2841
2857
|
|
|
2842
2858
|
/**
|
|
2843
2859
|
* Set this tile to use a full image
|
|
2844
2860
|
* @param {HTMLImageElement|OffscreenCanvas} image
|
|
2845
|
-
* @param {WebGLTexture} [glTexture] -
|
|
2861
|
+
* @param {WebGLTexture} [glTexture] - WebGL texture
|
|
2846
2862
|
* @return {TileInfo}
|
|
2847
2863
|
*/
|
|
2848
2864
|
setFullImage(image, glTexture)
|
|
@@ -2860,7 +2876,7 @@ class TextureInfo
|
|
|
2860
2876
|
/**
|
|
2861
2877
|
* Create a TextureInfo, called automatically by the engine
|
|
2862
2878
|
* @param {HTMLImageElement|OffscreenCanvas} image
|
|
2863
|
-
* @param {WebGLTexture} [glTexture] -
|
|
2879
|
+
* @param {WebGLTexture} [glTexture] - WebGL texture
|
|
2864
2880
|
*/
|
|
2865
2881
|
constructor(image, glTexture)
|
|
2866
2882
|
{
|
|
@@ -2870,7 +2886,7 @@ class TextureInfo
|
|
|
2870
2886
|
this.size = vec2(image.width, image.height);
|
|
2871
2887
|
/** @property {Vector2} - inverse of the size, cached for rendering */
|
|
2872
2888
|
this.sizeInverse = vec2(1/image.width, 1/image.height);
|
|
2873
|
-
/** @property {WebGLTexture} -
|
|
2889
|
+
/** @property {WebGLTexture} - WebGL texture */
|
|
2874
2890
|
this.glTexture = glTexture;
|
|
2875
2891
|
}
|
|
2876
2892
|
|
|
@@ -2886,28 +2902,25 @@ class TextureInfo
|
|
|
2886
2902
|
// Drawing functions
|
|
2887
2903
|
|
|
2888
2904
|
/** Draw textured tile centered in world space, with color applied if using WebGL
|
|
2889
|
-
* @param {Vector2}
|
|
2890
|
-
* @param {Vector2}
|
|
2891
|
-
* @param {TileInfo}[tileInfo]
|
|
2892
|
-
* @param {Color}
|
|
2893
|
-
* @param {number}
|
|
2894
|
-
* @param {boolean}
|
|
2895
|
-
* @param {Color}
|
|
2896
|
-
* @param {boolean}
|
|
2897
|
-
* @param {boolean}
|
|
2905
|
+
* @param {Vector2} pos - Center of the tile in world space
|
|
2906
|
+
* @param {Vector2} [size=(1,1)] - Size of the tile in world space
|
|
2907
|
+
* @param {TileInfo} [tileInfo] - Tile info to use, untextured if undefined
|
|
2908
|
+
* @param {Color} [color=(1,1,1,1)] - Color to modulate with
|
|
2909
|
+
* @param {number} [angle] - Angle to rotate by
|
|
2910
|
+
* @param {boolean} [mirror] - Is image flipped along the Y axis?
|
|
2911
|
+
* @param {Color} [additiveColor] - Additive color to be applied if any
|
|
2912
|
+
* @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering?
|
|
2913
|
+
* @param {boolean} [screenSpace=false] - Are the pos and size are in screen space?
|
|
2898
2914
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
2899
2915
|
* @memberof Draw */
|
|
2900
|
-
function drawTile(pos, size=new Vector2(1), tileInfo, color=
|
|
2916
|
+
function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
|
|
2901
2917
|
angle=0, mirror, additiveColor, useWebGL=glEnable, screenSpace, context)
|
|
2902
2918
|
{
|
|
2903
|
-
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
2904
2919
|
ASSERT(isVector2(pos) && pos.isValid(), 'drawTile pos should be a vec2');
|
|
2905
2920
|
ASSERT(isVector2(size) && size.isValid(), 'drawTile size should be a vec2');
|
|
2906
2921
|
ASSERT(isColor(color) && (!additiveColor || isColor(additiveColor)), 'drawTile color is invalid');
|
|
2907
2922
|
ASSERT(isNumber(angle), 'drawTile angle should be a number');
|
|
2908
|
-
|
|
2909
|
-
if (color.a <= 0 && (!additiveColor || additiveColor.a <= 0) || !size.x || !size.y)
|
|
2910
|
-
return; // completely invisible, skip render
|
|
2923
|
+
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
2911
2924
|
|
|
2912
2925
|
const textureInfo = tileInfo && tileInfo.textureInfo;
|
|
2913
2926
|
if (useWebGL)
|
|
@@ -2931,22 +2944,22 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=new Color,
|
|
|
2931
2944
|
{
|
|
2932
2945
|
const tileImageFixBleedX = sizeInverse.x*tileFixBleedScale;
|
|
2933
2946
|
const tileImageFixBleedY = sizeInverse.y*tileFixBleedScale;
|
|
2934
|
-
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
2935
|
-
x + tileImageFixBleedX, y + tileImageFixBleedY,
|
|
2936
|
-
x - tileImageFixBleedX + w, y - tileImageFixBleedY + h,
|
|
2937
|
-
color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
|
|
2947
|
+
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
2948
|
+
x + tileImageFixBleedX, y + tileImageFixBleedY,
|
|
2949
|
+
x - tileImageFixBleedX + w, y - tileImageFixBleedY + h,
|
|
2950
|
+
color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
|
|
2938
2951
|
}
|
|
2939
2952
|
else
|
|
2940
2953
|
{
|
|
2941
|
-
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
2942
|
-
x, y, x + w, y + h,
|
|
2943
|
-
color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
|
|
2954
|
+
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
2955
|
+
x, y, x + w, y + h,
|
|
2956
|
+
color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
|
|
2944
2957
|
}
|
|
2945
2958
|
}
|
|
2946
2959
|
else
|
|
2947
2960
|
{
|
|
2948
2961
|
// if no tile info, force untextured
|
|
2949
|
-
glDraw(pos.x, pos.y, size.x, size.y, angle, 0, 0, 0, 0, 0, color.rgbaInt());
|
|
2962
|
+
glDraw(pos.x, pos.y, size.x, size.y, angle, 0, 0, 0, 0, 0, color.rgbaInt());
|
|
2950
2963
|
}
|
|
2951
2964
|
}
|
|
2952
2965
|
else
|
|
@@ -2984,8 +2997,8 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=new Color,
|
|
|
2984
2997
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
2985
2998
|
* @memberof Draw */
|
|
2986
2999
|
function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
2987
|
-
{
|
|
2988
|
-
drawTile(pos, size, undefined, color, angle, false, undefined, useWebGL, screenSpace, context);
|
|
3000
|
+
{
|
|
3001
|
+
drawTile(pos, size, undefined, color, angle, false, undefined, useWebGL, screenSpace, context);
|
|
2989
3002
|
}
|
|
2990
3003
|
|
|
2991
3004
|
/** Draw colored line between two points
|
|
@@ -3008,6 +3021,30 @@ function drawLine(posA, posB, thickness=.1, color, pos=vec2(), angle=0, useWebGL
|
|
|
3008
3021
|
drawRect(pos, size, color, angle, useWebGL, screenSpace, context);
|
|
3009
3022
|
}
|
|
3010
3023
|
|
|
3024
|
+
/** Draw colored regular polygon using passed in number of sides
|
|
3025
|
+
* @param {Vector2} pos
|
|
3026
|
+
* @param {Vector2} [size=(1,1)]
|
|
3027
|
+
* @param {number} [sides]
|
|
3028
|
+
* @param {Color} [color=(1,1,1,1)]
|
|
3029
|
+
* @param {number} [angle]
|
|
3030
|
+
* @param {number} [lineWidth]
|
|
3031
|
+
* @param {Color} [lineColor=(0,0,0,1)]
|
|
3032
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
3033
|
+
* @param {boolean} [screenSpace]
|
|
3034
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
3035
|
+
* @memberof Draw */
|
|
3036
|
+
function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, lineColor=BLACK, angle=0, useWebGL=glEnable, screenSpace=false, context)
|
|
3037
|
+
{
|
|
3038
|
+
// build regular polygon points
|
|
3039
|
+
const points = [];
|
|
3040
|
+
for (let i=sides; i--;)
|
|
3041
|
+
{
|
|
3042
|
+
const a = (i/sides)*PI*2;
|
|
3043
|
+
points.push(vec2(Math.sin(a)*size.x, Math.cos(a)*size.y));
|
|
3044
|
+
}
|
|
3045
|
+
drawPoly(points, color, lineWidth, lineColor, pos, angle, useWebGL, screenSpace, context);
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3011
3048
|
/** Draw colored polygon using passed in points
|
|
3012
3049
|
* @param {Array<Vector2>} points - Array of Vector2 points
|
|
3013
3050
|
* @param {Color} [color=(1,1,1,1)]
|
|
@@ -3015,33 +3052,49 @@ function drawLine(posA, posB, thickness=.1, color, pos=vec2(), angle=0, useWebGL
|
|
|
3015
3052
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
3016
3053
|
* @param {Vector2} [pos=(0,0)] - Offset to apply
|
|
3017
3054
|
* @param {number} [angle] - Angle to rotate by
|
|
3018
|
-
* @param {boolean} [useWebGL]
|
|
3055
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
3019
3056
|
* @param {boolean} [screenSpace]
|
|
3020
|
-
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context
|
|
3057
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
3021
3058
|
* @memberof Draw */
|
|
3022
|
-
function drawPoly(points, color=
|
|
3059
|
+
function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(), angle=0, useWebGL=glEnable, screenSpace=false, context=undefined)
|
|
3023
3060
|
{
|
|
3024
3061
|
ASSERT(isVector2(pos) && pos.isValid(), 'drawPoly pos should be a vec2');
|
|
3025
3062
|
ASSERT(Array.isArray(points), 'drawPoly points should be an array');
|
|
3026
3063
|
ASSERT(isColor(color) && isColor(lineColor), 'drawPoly color is invalid');
|
|
3027
3064
|
ASSERT(isNumber(lineWidth), 'drawPoly lineWidth should be a number');
|
|
3028
3065
|
ASSERT(isNumber(angle), 'drawPoly angle should be a number');
|
|
3029
|
-
ASSERT(!useWebGL, '
|
|
3030
|
-
|
|
3066
|
+
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
3067
|
+
if (useWebGL)
|
|
3031
3068
|
{
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
context.lineTo(point.x, point.y);
|
|
3035
|
-
context.closePath();
|
|
3036
|
-
context.fillStyle = color.toString();
|
|
3037
|
-
context.fill();
|
|
3038
|
-
if (lineWidth)
|
|
3069
|
+
let scale = 1;
|
|
3070
|
+
if (screenSpace)
|
|
3039
3071
|
{
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3072
|
+
// convert to world space
|
|
3073
|
+
pos = screenToWorld(pos);
|
|
3074
|
+
scale = 1/cameraScale;
|
|
3043
3075
|
}
|
|
3044
|
-
|
|
3076
|
+
glDrawPointsTransform(points, color.rgbaInt(), pos.x, pos.y, scale, scale, angle);
|
|
3077
|
+
if (lineWidth > 0)
|
|
3078
|
+
glDrawOutlineTransform(points, lineColor.rgbaInt(), lineWidth, pos.x, pos.y, scale, scale, angle);
|
|
3079
|
+
}
|
|
3080
|
+
else
|
|
3081
|
+
{
|
|
3082
|
+
drawCanvas2D(pos, vec2(1), angle, false, context=>
|
|
3083
|
+
{
|
|
3084
|
+
context.fillStyle = color.toString();
|
|
3085
|
+
context.beginPath();
|
|
3086
|
+
for (const point of points)
|
|
3087
|
+
context.lineTo(point.x, point.y);
|
|
3088
|
+
context.closePath();
|
|
3089
|
+
context.fill();
|
|
3090
|
+
if (lineWidth)
|
|
3091
|
+
{
|
|
3092
|
+
context.strokeStyle = lineColor.toString();
|
|
3093
|
+
context.lineWidth = lineWidth;
|
|
3094
|
+
context.stroke();
|
|
3095
|
+
}
|
|
3096
|
+
}, screenSpace, context);
|
|
3097
|
+
}
|
|
3045
3098
|
}
|
|
3046
3099
|
|
|
3047
3100
|
/** Draw colored ellipse using passed in point
|
|
@@ -3051,32 +3104,41 @@ function drawPoly(points, color=new Color, lineWidth=0, lineColor=BLACK, pos=vec
|
|
|
3051
3104
|
* @param {number} [angle]
|
|
3052
3105
|
* @param {number} [lineWidth]
|
|
3053
3106
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
3054
|
-
* @param {boolean} [useWebGL]
|
|
3107
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
3055
3108
|
* @param {boolean} [screenSpace]
|
|
3056
|
-
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context
|
|
3109
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
3057
3110
|
* @memberof Draw */
|
|
3058
|
-
function drawEllipse(pos, size=vec2(1), color=
|
|
3111
|
+
function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineColor=BLACK, useWebGL=glEnable, screenSpace=false, context)
|
|
3059
3112
|
{
|
|
3060
3113
|
ASSERT(isVector2(pos) && pos.isValid(), 'drawEllipse pos should be a vec2');
|
|
3061
3114
|
ASSERT(isVector2(size) && size.isValid(), 'drawEllipse size should be a vec2');
|
|
3062
3115
|
ASSERT(isColor(color) && isColor(lineColor), 'drawEllipse color is invalid');
|
|
3063
3116
|
ASSERT(isNumber(angle), 'drawEllipse angle should be a number');
|
|
3064
3117
|
ASSERT(isNumber(lineWidth), 'drawEllipse lineWidth should be a number');
|
|
3065
|
-
ASSERT(lineWidth>=0 && lineWidth < size.x && lineWidth < size.y, 'drawEllipse invalid lineWidth');
|
|
3066
|
-
ASSERT(!useWebGL, '
|
|
3067
|
-
|
|
3118
|
+
ASSERT(lineWidth >= 0 && lineWidth < size.x && lineWidth < size.y, 'drawEllipse invalid lineWidth');
|
|
3119
|
+
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
3120
|
+
if (useWebGL)
|
|
3068
3121
|
{
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3122
|
+
// draw as a regular polygon
|
|
3123
|
+
const sides = glCircleSides;
|
|
3124
|
+
drawRegularPoly(pos, size, sides, color, lineWidth, lineColor, angle, useWebGL, screenSpace, context);
|
|
3125
|
+
}
|
|
3126
|
+
else
|
|
3127
|
+
{
|
|
3128
|
+
drawCanvas2D(pos, vec2(1), angle, false, context=>
|
|
3074
3129
|
{
|
|
3075
|
-
context.
|
|
3076
|
-
context.
|
|
3077
|
-
context.
|
|
3078
|
-
|
|
3079
|
-
|
|
3130
|
+
context.fillStyle = color.toString();
|
|
3131
|
+
context.beginPath();
|
|
3132
|
+
context.ellipse(0, 0, size.x, size.y, 0, 0, 9);
|
|
3133
|
+
context.fill();
|
|
3134
|
+
if (lineWidth)
|
|
3135
|
+
{
|
|
3136
|
+
context.strokeStyle = lineColor.toString();
|
|
3137
|
+
context.lineWidth = lineWidth;
|
|
3138
|
+
context.stroke();
|
|
3139
|
+
}
|
|
3140
|
+
}, screenSpace, context);
|
|
3141
|
+
}
|
|
3080
3142
|
}
|
|
3081
3143
|
|
|
3082
3144
|
/** Draw colored circle using passed in point
|
|
@@ -3085,11 +3147,11 @@ function drawEllipse(pos, size=vec2(1), color=new Color, angle=0, lineWidth=0, l
|
|
|
3085
3147
|
* @param {Color} [color=(1,1,1,1)]
|
|
3086
3148
|
* @param {number} [lineWidth=0]
|
|
3087
3149
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
3088
|
-
* @param {boolean} [useWebGL]
|
|
3089
|
-
* @param {boolean} [screenSpace
|
|
3090
|
-
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context
|
|
3150
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
3151
|
+
* @param {boolean} [screenSpace]
|
|
3152
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
3091
3153
|
* @memberof Draw */
|
|
3092
|
-
function drawCircle(pos, radius=1, color=
|
|
3154
|
+
function drawCircle(pos, radius=1, color=WHITE, lineWidth=0, lineColor=BLACK, useWebGL=glEnable, screenSpace=false, context)
|
|
3093
3155
|
{ drawEllipse(pos, vec2(radius), color, 0, lineWidth, lineColor, useWebGL, screenSpace, context); }
|
|
3094
3156
|
|
|
3095
3157
|
/** Draw directly to a 2d canvas context in world space
|
|
@@ -3098,7 +3160,7 @@ function drawCircle(pos, radius=1, color=new Color, lineWidth=0, lineColor=BLACK
|
|
|
3098
3160
|
* @param {number} angle
|
|
3099
3161
|
* @param {boolean} [mirror]
|
|
3100
3162
|
* @param {Function} [drawFunction]
|
|
3101
|
-
* @param {boolean}
|
|
3163
|
+
* @param {boolean} [screenSpace=false]
|
|
3102
3164
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
3103
3165
|
* @memberof Draw */
|
|
3104
3166
|
function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpace=false, context=drawContext)
|
|
@@ -3168,7 +3230,7 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
|
|
|
3168
3230
|
* @param {number} [maxWidth]
|
|
3169
3231
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
3170
3232
|
* @memberof Draw */
|
|
3171
|
-
function drawTextScreen(text, pos, size=1, color=
|
|
3233
|
+
function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, maxWidth, context=overlayContext)
|
|
3172
3234
|
{
|
|
3173
3235
|
context.fillStyle = color.toString();
|
|
3174
3236
|
context.strokeStyle = lineColor.toString();
|
|
@@ -3200,7 +3262,7 @@ function screenToWorld(screenPos)
|
|
|
3200
3262
|
{
|
|
3201
3263
|
let cameraPosRelativeX = (screenPos.x - mainCanvasSize.x/2 + .5) / cameraScale;
|
|
3202
3264
|
let cameraPosRelativeY = (screenPos.y - mainCanvasSize.y/2 + .5) / -cameraScale;
|
|
3203
|
-
if (cameraAngle)
|
|
3265
|
+
if (cameraAngle)
|
|
3204
3266
|
{
|
|
3205
3267
|
// apply camera rotation
|
|
3206
3268
|
const cos = Math.cos(-cameraAngle), sin = Math.sin(-cameraAngle);
|
|
@@ -3293,7 +3355,7 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
|
|
|
3293
3355
|
{
|
|
3294
3356
|
// white texture with no additive alpha, no need to tint
|
|
3295
3357
|
context.globalAlpha = color.a;
|
|
3296
|
-
context.drawImage(image, sx+sx2, sy+sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
|
|
3358
|
+
context.drawImage(image, sx+sx2, sy+sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
|
|
3297
3359
|
context.globalAlpha = 1;
|
|
3298
3360
|
}
|
|
3299
3361
|
else
|
|
@@ -3314,7 +3376,7 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
|
|
|
3314
3376
|
for (let i = 0; i < data.length; ++i)
|
|
3315
3377
|
data[i] = data[i] * colorMultiply[i&3] + colorAdd[i&3] |0;
|
|
3316
3378
|
workContext.putImageData(imageData, 0, 0);
|
|
3317
|
-
context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
|
|
3379
|
+
context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
|
|
3318
3380
|
}
|
|
3319
3381
|
else
|
|
3320
3382
|
{
|
|
@@ -3327,7 +3389,7 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
|
|
|
3327
3389
|
}
|
|
3328
3390
|
workContext.putImageData(imageData, 0, 0);
|
|
3329
3391
|
context.globalAlpha = color.a;
|
|
3330
|
-
context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
|
|
3392
|
+
context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
|
|
3331
3393
|
context.globalAlpha = 1;
|
|
3332
3394
|
}
|
|
3333
3395
|
}
|
|
@@ -3354,7 +3416,7 @@ function toggleFullscreen()
|
|
|
3354
3416
|
}
|
|
3355
3417
|
|
|
3356
3418
|
/** Set the cursor style
|
|
3357
|
-
* @param {string} cursorStyle - CSS cursor style (auto, none, crosshair, etc)
|
|
3419
|
+
* @param {string} [cursorStyle] - CSS cursor style (auto, none, crosshair, etc)
|
|
3358
3420
|
* @memberof Draw */
|
|
3359
3421
|
function setCursor(cursorStyle = 'auto')
|
|
3360
3422
|
{
|
|
@@ -3366,7 +3428,7 @@ function setCursor(cursorStyle = 'auto')
|
|
|
3366
3428
|
|
|
3367
3429
|
let engineFontImage;
|
|
3368
3430
|
|
|
3369
|
-
/**
|
|
3431
|
+
/**
|
|
3370
3432
|
* Font Image Object - Draw text on a 2D canvas by using characters in an image
|
|
3371
3433
|
* - 96 characters (from space to tilde) are stored in an image
|
|
3372
3434
|
* - Uses a default 8x8 font if none is supplied
|
|
@@ -3374,7 +3436,7 @@ let engineFontImage;
|
|
|
3374
3436
|
* @example
|
|
3375
3437
|
* // use built in font
|
|
3376
3438
|
* const font = new FontImage;
|
|
3377
|
-
*
|
|
3439
|
+
*
|
|
3378
3440
|
* // draw text
|
|
3379
3441
|
* font.drawTextScreen("LittleJS\nHello World!", vec2(200, 50));
|
|
3380
3442
|
*/
|
|
@@ -3384,7 +3446,6 @@ class FontImage
|
|
|
3384
3446
|
* @param {HTMLImageElement} [image] - Image for the font, if undefined default font is used
|
|
3385
3447
|
* @param {Vector2} [tileSize=(8,8)] - Size of the font source tiles
|
|
3386
3448
|
* @param {Vector2} [paddingSize=(0,1)] - How much extra space to add between characters
|
|
3387
|
-
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext] - context to draw to
|
|
3388
3449
|
*/
|
|
3389
3450
|
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), context=overlayContext)
|
|
3390
3451
|
{
|
|
@@ -3398,7 +3459,6 @@ class FontImage
|
|
|
3398
3459
|
this.image = image || engineFontImage;
|
|
3399
3460
|
this.tileSize = tileSize;
|
|
3400
3461
|
this.paddingSize = paddingSize;
|
|
3401
|
-
this.context = context;
|
|
3402
3462
|
}
|
|
3403
3463
|
|
|
3404
3464
|
/** Draw text in world space using the image font
|
|
@@ -3406,23 +3466,32 @@ class FontImage
|
|
|
3406
3466
|
* @param {Vector2} pos
|
|
3407
3467
|
* @param {number} [scale=.25]
|
|
3408
3468
|
* @param {boolean} [center]
|
|
3469
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D}[context=drawContext]
|
|
3409
3470
|
*/
|
|
3410
|
-
drawText(text, pos, scale=1, center)
|
|
3471
|
+
drawText(text, pos, scale=1, center, context=drawContext)
|
|
3411
3472
|
{
|
|
3412
|
-
this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center);
|
|
3473
|
+
this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center, context);
|
|
3413
3474
|
}
|
|
3414
3475
|
|
|
3415
|
-
/** Draw text in
|
|
3476
|
+
/** Draw text on overlay canvas in world space using the image font
|
|
3477
|
+
* @param {string} text
|
|
3478
|
+
* @param {Vector2} pos
|
|
3479
|
+
* @param {number} [scale]
|
|
3480
|
+
* @param {boolean} [center]
|
|
3481
|
+
*/
|
|
3482
|
+
drawTextOverlay(text, pos, scale=4, center)
|
|
3483
|
+
{ this.drawText(text, pos, scale, center, overlayContext); }
|
|
3484
|
+
|
|
3485
|
+
/** Draw text on overlay canvas in screen space using the image font
|
|
3416
3486
|
* @param {string} text
|
|
3417
3487
|
* @param {Vector2} pos
|
|
3418
3488
|
* @param {number} [scale]
|
|
3419
3489
|
* @param {boolean} [center]
|
|
3490
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
3420
3491
|
*/
|
|
3421
|
-
drawTextScreen(text, pos, scale=4, center)
|
|
3492
|
+
drawTextScreen(text, pos, scale=4, center, context=overlayContext)
|
|
3422
3493
|
{
|
|
3423
|
-
const context = this.context;
|
|
3424
3494
|
context.save();
|
|
3425
|
-
|
|
3426
3495
|
const size = this.tileSize;
|
|
3427
3496
|
const drawSize = size.add(this.paddingSize).scale(scale);
|
|
3428
3497
|
const cols = this.image.width / this.tileSize.x |0;
|
|
@@ -3441,15 +3510,14 @@ class FontImage
|
|
|
3441
3510
|
const x = tile % cols;
|
|
3442
3511
|
const y = tile / cols |0;
|
|
3443
3512
|
const drawPos = pos.add(vec2(j,i).multiply(drawSize));
|
|
3444
|
-
context.drawImage(this.image, x * size.x, y * size.y, size.x, size.y,
|
|
3513
|
+
context.drawImage(this.image, x * size.x, y * size.y, size.x, size.y,
|
|
3445
3514
|
drawPos.x - centerOffset, drawPos.y, size.x * scale, size.y * scale);
|
|
3446
3515
|
}
|
|
3447
3516
|
});
|
|
3448
|
-
|
|
3449
3517
|
context.restore();
|
|
3450
3518
|
}
|
|
3451
3519
|
}
|
|
3452
|
-
/**
|
|
3520
|
+
/**
|
|
3453
3521
|
* LittleJS Input System
|
|
3454
3522
|
* - Tracks keyboard down, pressed, and released
|
|
3455
3523
|
* - Tracks mouse buttons, position, and wheel
|
|
@@ -3465,10 +3533,10 @@ class FontImage
|
|
|
3465
3533
|
* @return {boolean}
|
|
3466
3534
|
* @memberof Input */
|
|
3467
3535
|
function keyIsDown(key, device=0)
|
|
3468
|
-
{
|
|
3536
|
+
{
|
|
3469
3537
|
ASSERT(key !== undefined, 'key is undefined');
|
|
3470
|
-
ASSERT(device > 0 || typeof key
|
|
3471
|
-
return inputData[device] && !!(inputData[device][key] & 1);
|
|
3538
|
+
ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
|
|
3539
|
+
return inputData[device] && !!(inputData[device][key] & 1);
|
|
3472
3540
|
}
|
|
3473
3541
|
|
|
3474
3542
|
/** Returns true if device key was pressed this frame
|
|
@@ -3477,10 +3545,10 @@ function keyIsDown(key, device=0)
|
|
|
3477
3545
|
* @return {boolean}
|
|
3478
3546
|
* @memberof Input */
|
|
3479
3547
|
function keyWasPressed(key, device=0)
|
|
3480
|
-
{
|
|
3548
|
+
{
|
|
3481
3549
|
ASSERT(key !== undefined, 'key is undefined');
|
|
3482
|
-
ASSERT(device > 0 || typeof key
|
|
3483
|
-
return inputData[device] && !!(inputData[device][key] & 2);
|
|
3550
|
+
ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
|
|
3551
|
+
return inputData[device] && !!(inputData[device][key] & 2);
|
|
3484
3552
|
}
|
|
3485
3553
|
|
|
3486
3554
|
/** Returns true if device key was released this frame
|
|
@@ -3489,9 +3557,9 @@ function keyWasPressed(key, device=0)
|
|
|
3489
3557
|
* @return {boolean}
|
|
3490
3558
|
* @memberof Input */
|
|
3491
3559
|
function keyWasReleased(key, device=0)
|
|
3492
|
-
{
|
|
3560
|
+
{
|
|
3493
3561
|
ASSERT(key !== undefined, 'key is undefined');
|
|
3494
|
-
ASSERT(device > 0 || typeof key
|
|
3562
|
+
ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
|
|
3495
3563
|
return inputData[device] && !!(inputData[device][key] & 4);
|
|
3496
3564
|
}
|
|
3497
3565
|
|
|
@@ -3613,7 +3681,7 @@ function gamepadWasReleased(button, gamepad=0)
|
|
|
3613
3681
|
* @param {number} [gamepad]
|
|
3614
3682
|
* @return {Vector2}
|
|
3615
3683
|
* @memberof Input */
|
|
3616
|
-
function gamepadStick(stick,
|
|
3684
|
+
function gamepadStick(stick, gamepad=0)
|
|
3617
3685
|
{ return gamepadStickData[gamepad] ? gamepadStickData[gamepad][stick] || vec2() : vec2(); }
|
|
3618
3686
|
|
|
3619
3687
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -3690,10 +3758,10 @@ function inputInit()
|
|
|
3690
3758
|
{
|
|
3691
3759
|
// handle remapping wasd keys to directions
|
|
3692
3760
|
return inputWASDEmulateDirection ?
|
|
3693
|
-
c
|
|
3694
|
-
c
|
|
3695
|
-
c
|
|
3696
|
-
c
|
|
3761
|
+
c === 'KeyW' ? 'ArrowUp' :
|
|
3762
|
+
c === 'KeyS' ? 'ArrowDown' :
|
|
3763
|
+
c === 'KeyA' ? 'ArrowLeft' :
|
|
3764
|
+
c === 'KeyD' ? 'ArrowRight' : c : c;
|
|
3697
3765
|
}
|
|
3698
3766
|
function onMouseDown(e)
|
|
3699
3767
|
{
|
|
@@ -3701,9 +3769,9 @@ function inputInit()
|
|
|
3701
3769
|
return;
|
|
3702
3770
|
|
|
3703
3771
|
// fix stalled audio requiring user interaction
|
|
3704
|
-
if (soundEnable && !headlessMode && audioContext &&
|
|
3772
|
+
if (soundEnable && !headlessMode && audioContext && !audioIsRunning())
|
|
3705
3773
|
audioContext.resume();
|
|
3706
|
-
|
|
3774
|
+
|
|
3707
3775
|
isUsingGamepad = false;
|
|
3708
3776
|
inputData[0][e.button] = 3;
|
|
3709
3777
|
mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
|
|
@@ -3746,8 +3814,8 @@ function gamepadsUpdate()
|
|
|
3746
3814
|
const applyDeadZones = (v)=>
|
|
3747
3815
|
{
|
|
3748
3816
|
const min=.3, max=.8;
|
|
3749
|
-
const deadZone = (v)=>
|
|
3750
|
-
v >
|
|
3817
|
+
const deadZone = (v)=>
|
|
3818
|
+
v > min ? percent(v, min, max) :
|
|
3751
3819
|
v < -min ? -percent(-v, min, max) : 0;
|
|
3752
3820
|
return vec2(deadZone(v.x), deadZone(-v.y)).clampLength();
|
|
3753
3821
|
}
|
|
@@ -3755,30 +3823,29 @@ function gamepadsUpdate()
|
|
|
3755
3823
|
// update touch gamepad if enabled
|
|
3756
3824
|
if (touchGamepadEnable && isTouchDevice)
|
|
3757
3825
|
{
|
|
3758
|
-
|
|
3759
|
-
|
|
3826
|
+
if (!touchGamepadTimer.isSet())
|
|
3827
|
+
return;
|
|
3828
|
+
|
|
3829
|
+
// read virtual analog stick
|
|
3830
|
+
const sticks = gamepadStickData[0] || (gamepadStickData[0] = []);
|
|
3831
|
+
sticks[0] = vec2();
|
|
3832
|
+
if (touchGamepadAnalog)
|
|
3833
|
+
sticks[0] = applyDeadZones(touchGamepadStick);
|
|
3834
|
+
else if (touchGamepadStick.lengthSquared() > .3)
|
|
3760
3835
|
{
|
|
3761
|
-
//
|
|
3762
|
-
|
|
3763
|
-
sticks[0] =
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
else if (touchGamepadStick.lengthSquared() > .3)
|
|
3767
|
-
{
|
|
3768
|
-
// convert to 8 way dpad
|
|
3769
|
-
sticks[0].x = Math.round(touchGamepadStick.x);
|
|
3770
|
-
sticks[0].y = -Math.round(touchGamepadStick.y);
|
|
3771
|
-
sticks[0] = sticks[0].clampLength();
|
|
3772
|
-
}
|
|
3836
|
+
// convert to 8 way dpad
|
|
3837
|
+
sticks[0].x = Math.round(touchGamepadStick.x);
|
|
3838
|
+
sticks[0].y = -Math.round(touchGamepadStick.y);
|
|
3839
|
+
sticks[0] = sticks[0].clampLength();
|
|
3840
|
+
}
|
|
3773
3841
|
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
}
|
|
3842
|
+
// read virtual gamepad buttons
|
|
3843
|
+
const data = inputData[1] || (inputData[1] = []);
|
|
3844
|
+
for (let i=10; i--;)
|
|
3845
|
+
{
|
|
3846
|
+
const j = i === 3 ? 2 : i === 2 ? 3 : i; // fix button locations
|
|
3847
|
+
const wasDown = gamepadIsDown(j,0);
|
|
3848
|
+
data[j] = touchGamepadButtons[i] ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
|
|
3782
3849
|
}
|
|
3783
3850
|
}
|
|
3784
3851
|
|
|
@@ -3804,7 +3871,7 @@ function gamepadsUpdate()
|
|
|
3804
3871
|
// read analog sticks
|
|
3805
3872
|
for (let j = 0; j < gamepad.axes.length-1; j+=2)
|
|
3806
3873
|
sticks[j>>1] = applyDeadZones(vec2(gamepad.axes[j],gamepad.axes[j+1]));
|
|
3807
|
-
|
|
3874
|
+
|
|
3808
3875
|
// read buttons
|
|
3809
3876
|
for (let j = gamepad.buttons.length; j--;)
|
|
3810
3877
|
{
|
|
@@ -3820,14 +3887,14 @@ function gamepadsUpdate()
|
|
|
3820
3887
|
{
|
|
3821
3888
|
// copy dpad to left analog stick when pressed
|
|
3822
3889
|
const dpad = vec2(
|
|
3823
|
-
(gamepadIsDown(15,i)&&1) - (gamepadIsDown(14,i)&&1),
|
|
3890
|
+
(gamepadIsDown(15,i)&&1) - (gamepadIsDown(14,i)&&1),
|
|
3824
3891
|
(gamepadIsDown(12,i)&&1) - (gamepadIsDown(13,i)&&1));
|
|
3825
3892
|
if (dpad.lengthSquared())
|
|
3826
3893
|
sticks[0] = dpad.clampLength();
|
|
3827
3894
|
}
|
|
3828
3895
|
|
|
3829
3896
|
// disable touch gamepad if using real gamepad
|
|
3830
|
-
touchGamepadEnable && isUsingGamepad && touchGamepadTimer.unset();
|
|
3897
|
+
touchGamepadEnable && isUsingGamepad && touchGamepadTimer.unset();
|
|
3831
3898
|
}
|
|
3832
3899
|
}
|
|
3833
3900
|
}
|
|
@@ -3852,20 +3919,13 @@ function vibrateStop() { vibrate(0); }
|
|
|
3852
3919
|
const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
3853
3920
|
|
|
3854
3921
|
// touch gamepad internal variables
|
|
3855
|
-
let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
|
|
3922
|
+
let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick = vec2();
|
|
3856
3923
|
|
|
3857
3924
|
// enable touch input mouse passthrough
|
|
3858
3925
|
function touchInputInit()
|
|
3859
3926
|
{
|
|
3860
3927
|
// add non passive touch event listeners
|
|
3861
3928
|
let handleTouch = handleTouchDefault;
|
|
3862
|
-
if (touchGamepadEnable)
|
|
3863
|
-
{
|
|
3864
|
-
// touch input internal variables
|
|
3865
|
-
handleTouch = handleTouchGamepad;
|
|
3866
|
-
touchGamepadButtons = [];
|
|
3867
|
-
touchGamepadStick = vec2();
|
|
3868
|
-
}
|
|
3869
3929
|
document.addEventListener('touchstart', (e) => handleTouch(e), { passive: false });
|
|
3870
3930
|
document.addEventListener('touchmove', (e) => handleTouch(e), { passive: false });
|
|
3871
3931
|
document.addEventListener('touchend', (e) => handleTouch(e), { passive: false });
|
|
@@ -3874,8 +3934,15 @@ function touchInputInit()
|
|
|
3874
3934
|
let wasTouching;
|
|
3875
3935
|
function handleTouchDefault(e)
|
|
3876
3936
|
{
|
|
3937
|
+
if (!touchInputEnable)
|
|
3938
|
+
return;
|
|
3939
|
+
|
|
3940
|
+
// route touch to gamepad
|
|
3941
|
+
if (touchGamepadEnable)
|
|
3942
|
+
handleTouchGamepad(e);
|
|
3943
|
+
|
|
3877
3944
|
// fix stalled audio requiring user interaction
|
|
3878
|
-
if (soundEnable && !headlessMode && audioContext &&
|
|
3945
|
+
if (soundEnable && !headlessMode && audioContext && !audioIsRunning())
|
|
3879
3946
|
audioContext.resume();
|
|
3880
3947
|
|
|
3881
3948
|
// check if touching and pass to mouse events
|
|
@@ -3904,7 +3971,7 @@ function touchInputInit()
|
|
|
3904
3971
|
// prevent default handling like copy and magnifier lens
|
|
3905
3972
|
if (inputPreventDefault && document.hasFocus()) // allow document to get focus
|
|
3906
3973
|
e.preventDefault();
|
|
3907
|
-
|
|
3974
|
+
|
|
3908
3975
|
// must return true so the document will get focus
|
|
3909
3976
|
return true;
|
|
3910
3977
|
}
|
|
@@ -3916,7 +3983,7 @@ function touchInputInit()
|
|
|
3916
3983
|
touchGamepadStick = vec2();
|
|
3917
3984
|
touchGamepadButtons = [];
|
|
3918
3985
|
isUsingGamepad = true;
|
|
3919
|
-
|
|
3986
|
+
|
|
3920
3987
|
const touching = e.touches.length;
|
|
3921
3988
|
if (touching)
|
|
3922
3989
|
{
|
|
@@ -3925,9 +3992,6 @@ function touchInputInit()
|
|
|
3925
3992
|
{
|
|
3926
3993
|
// touch anywhere to press start when paused
|
|
3927
3994
|
touchGamepadButtons[9] = 1;
|
|
3928
|
-
|
|
3929
|
-
// call default touch handler so normal touch events still work
|
|
3930
|
-
handleTouchDefault(e);
|
|
3931
3995
|
return;
|
|
3932
3996
|
}
|
|
3933
3997
|
}
|
|
@@ -3958,12 +4022,6 @@ function touchInputInit()
|
|
|
3958
4022
|
touchGamepadButtons[9] = 1;
|
|
3959
4023
|
}
|
|
3960
4024
|
}
|
|
3961
|
-
|
|
3962
|
-
// call default touch handler so normal touch events still work
|
|
3963
|
-
handleTouchDefault(e);
|
|
3964
|
-
|
|
3965
|
-
// must return true so the document will get focus
|
|
3966
|
-
return true;
|
|
3967
4025
|
}
|
|
3968
4026
|
}
|
|
3969
4027
|
|
|
@@ -3973,7 +4031,7 @@ function touchGamepadRender()
|
|
|
3973
4031
|
if (!touchInputEnable || !isTouchDevice || headlessMode) return;
|
|
3974
4032
|
if (!touchGamepadEnable || !touchGamepadTimer.isSet())
|
|
3975
4033
|
return;
|
|
3976
|
-
|
|
4034
|
+
|
|
3977
4035
|
// fade off when not touching or paused
|
|
3978
4036
|
const alpha = percent(touchGamepadTimer.get(), 4, 3);
|
|
3979
4037
|
if (!alpha || paused)
|
|
@@ -4004,11 +4062,11 @@ function touchGamepadRender()
|
|
|
4004
4062
|
const angle = i*PI/4;
|
|
4005
4063
|
context.arc(leftCenter.x, leftCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
|
|
4006
4064
|
i%2 && context.arc(leftCenter.x, leftCenter.y, touchGamepadSize*.33, angle, angle);
|
|
4007
|
-
i
|
|
4065
|
+
i===1 && context.fill();
|
|
4008
4066
|
}
|
|
4009
4067
|
context.stroke();
|
|
4010
4068
|
}
|
|
4011
|
-
|
|
4069
|
+
|
|
4012
4070
|
// draw right face buttons
|
|
4013
4071
|
const rightCenter = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
4014
4072
|
for (let i=4; i--;)
|
|
@@ -4043,8 +4101,8 @@ function pointerLockExit() { document.exitPointerLock && document.exitPointerLoc
|
|
|
4043
4101
|
/** Check if pointer is locked (true if locked)
|
|
4044
4102
|
* @return {boolean}
|
|
4045
4103
|
* @memberof Input */
|
|
4046
|
-
function pointerLockIsActive() { return document.pointerLockElement
|
|
4047
|
-
/**
|
|
4104
|
+
function pointerLockIsActive() { return document.pointerLockElement === mainCanvas; }
|
|
4105
|
+
/**
|
|
4048
4106
|
* LittleJS Audio System
|
|
4049
4107
|
* - <a href=https://killedbyapixel.github.io/ZzFX/>ZzFX Sound Effects</a> - ZzFX Sound Effect Generator
|
|
4050
4108
|
* - <a href=https://keithclark.github.io/ZzFXM/>ZzFXM Music</a> - ZzFXM Music System
|
|
@@ -4065,10 +4123,21 @@ let audioContext = new AudioContext;
|
|
|
4065
4123
|
* @memberof Audio */
|
|
4066
4124
|
let audioMasterGain;
|
|
4067
4125
|
|
|
4126
|
+
/** Default sample rate used for sounds
|
|
4127
|
+
* @default 44100
|
|
4128
|
+
* @memberof Audio */
|
|
4129
|
+
const audioDefaultSampleRate = 44100;
|
|
4130
|
+
|
|
4131
|
+
/** Check if the audio context is running and available for playback
|
|
4132
|
+
* @return {boolean} - True if the audio context is running
|
|
4133
|
+
* @memberof Audio */
|
|
4134
|
+
function audioIsRunning()
|
|
4135
|
+
{ return audioContext.state === 'running'; }
|
|
4136
|
+
|
|
4068
4137
|
function audioInit()
|
|
4069
4138
|
{
|
|
4070
4139
|
if (!soundEnable || headlessMode) return;
|
|
4071
|
-
|
|
4140
|
+
|
|
4072
4141
|
audioMasterGain = audioContext.createGain();
|
|
4073
4142
|
audioMasterGain.connect(audioContext.destination);
|
|
4074
4143
|
audioMasterGain.gain.value = soundVolume; // set starting value
|
|
@@ -4076,14 +4145,14 @@ function audioInit()
|
|
|
4076
4145
|
|
|
4077
4146
|
///////////////////////////////////////////////////////////////////////////////
|
|
4078
4147
|
|
|
4079
|
-
/**
|
|
4148
|
+
/**
|
|
4080
4149
|
* Sound Object - Stores a sound for later use and can be played positionally
|
|
4081
|
-
*
|
|
4150
|
+
*
|
|
4082
4151
|
* <a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a>
|
|
4083
4152
|
* @example
|
|
4084
4153
|
* // create a sound
|
|
4085
4154
|
* const sound_example = new Sound([.5,.5]);
|
|
4086
|
-
*
|
|
4155
|
+
*
|
|
4087
4156
|
* // play the sound
|
|
4088
4157
|
* sound_example.play();
|
|
4089
4158
|
*/
|
|
@@ -4100,33 +4169,41 @@ class Sound
|
|
|
4100
4169
|
|
|
4101
4170
|
/** @property {number} - World space max range of sound */
|
|
4102
4171
|
this.range = range;
|
|
4103
|
-
|
|
4104
4172
|
/** @property {number} - At what percentage of range should it start tapering */
|
|
4105
4173
|
this.taper = taper;
|
|
4106
|
-
|
|
4107
4174
|
/** @property {number} - How much to randomize frequency each time sound plays */
|
|
4108
4175
|
this.randomness = 0;
|
|
4176
|
+
/** @property {number} - Sample rate for this sound */
|
|
4177
|
+
this.sampleRate = audioDefaultSampleRate;
|
|
4178
|
+
/** @property {number} - Percentage of this sound currently loaded */
|
|
4179
|
+
this.loadedPercent = 0;
|
|
4109
4180
|
|
|
4181
|
+
// generate zzfx sound now for fast playback
|
|
4110
4182
|
if (zzfxSound)
|
|
4111
4183
|
{
|
|
4112
|
-
//
|
|
4113
|
-
const defaultRandomness = .05;
|
|
4114
|
-
this.randomness = zzfxSound[
|
|
4115
|
-
|
|
4184
|
+
// remove randomness so it can be applied on playback
|
|
4185
|
+
const randomnessIndex = 1, defaultRandomness = .05;
|
|
4186
|
+
this.randomness = zzfxSound[randomnessIndex] !== undefined ?
|
|
4187
|
+
zzfxSound[randomnessIndex] : defaultRandomness;
|
|
4188
|
+
zzfxSound[randomnessIndex] = 0;
|
|
4189
|
+
|
|
4190
|
+
// generate the zzfx samples
|
|
4116
4191
|
this.sampleChannels = [zzfxG(...zzfxSound)];
|
|
4117
|
-
this.
|
|
4192
|
+
this.loadedPercent = 1;
|
|
4118
4193
|
}
|
|
4119
4194
|
}
|
|
4120
4195
|
|
|
4121
4196
|
/** Play the sound
|
|
4122
|
-
*
|
|
4123
|
-
* @param {
|
|
4124
|
-
* @param {number} [
|
|
4125
|
-
* @param {number} [
|
|
4126
|
-
* @param {
|
|
4127
|
-
* @
|
|
4197
|
+
* Sounds may not play until a user interaction occurs
|
|
4198
|
+
* @param {Vector2} [pos] - World space position to play the sound if any
|
|
4199
|
+
* @param {number} [volume] - How much to scale volume by
|
|
4200
|
+
* @param {number} [pitch] - How much to scale pitch by
|
|
4201
|
+
* @param {number} [randomnessScale] - How much to scale pitch randomness
|
|
4202
|
+
* @param {boolean} [loop] - Should the sound loop?
|
|
4203
|
+
* @param {boolean} [paused] - Should the sound start paused
|
|
4204
|
+
* @return {SoundInstance} - The audio source node
|
|
4128
4205
|
*/
|
|
4129
|
-
play(pos, volume=1, pitch=1, randomnessScale=1, loop=false)
|
|
4206
|
+
play(pos, volume=1, pitch=1, randomnessScale=1, loop=false, paused=false)
|
|
4130
4207
|
{
|
|
4131
4208
|
if (!soundEnable || headlessMode) return;
|
|
4132
4209
|
if (!this.sampleChannels) return;
|
|
@@ -4149,75 +4226,55 @@ class Sound
|
|
|
4149
4226
|
// get pan from screen space coords
|
|
4150
4227
|
pan = worldToScreen(pos).x * 2/mainCanvas.width - 1;
|
|
4151
4228
|
}
|
|
4152
|
-
|
|
4153
|
-
// play the sound
|
|
4154
|
-
const playbackRate = pitch + pitch * this.randomness*randomnessScale*rand(-1,1);
|
|
4155
|
-
this.gainNode = audioContext.createGain();
|
|
4156
|
-
this.source = playSamples(this.sampleChannels, volume, playbackRate, pan, loop, this.sampleRate, this.gainNode);
|
|
4157
|
-
return this.source;
|
|
4158
|
-
}
|
|
4159
|
-
|
|
4160
|
-
/** Set the sound volume of the most recently played instance of this sound
|
|
4161
|
-
* @param {number} [volume] - How much to scale volume by
|
|
4162
|
-
*/
|
|
4163
|
-
setVolume(volume=1)
|
|
4164
|
-
{
|
|
4165
|
-
if (this.gainNode)
|
|
4166
|
-
this.gainNode.gain.value = volume;
|
|
4167
|
-
}
|
|
4168
|
-
|
|
4169
|
-
/** Stop the last instance of this sound that was played
|
|
4170
|
-
* @param {number} [fadeTime] - How long to fade out (seconds)
|
|
4171
|
-
*/
|
|
4172
|
-
stop(fadeTime=0)
|
|
4173
|
-
{
|
|
4174
|
-
if (!this.source)
|
|
4175
|
-
return;
|
|
4176
4229
|
|
|
4177
|
-
//
|
|
4178
|
-
const
|
|
4179
|
-
|
|
4180
|
-
this.gainNode.gain.linearRampToValueAtTime(1, startFade);
|
|
4181
|
-
this.gainNode.gain.linearRampToValueAtTime(0, endFade);
|
|
4182
|
-
this.source.stop(endFade);
|
|
4183
|
-
this.source = undefined;
|
|
4230
|
+
// Create and return sound instance
|
|
4231
|
+
const rate = pitch + pitch * this.randomness*randomnessScale*rand(-1,1);
|
|
4232
|
+
return new SoundInstance(this, volume, rate, pan, loop, paused);
|
|
4184
4233
|
}
|
|
4185
4234
|
|
|
4186
|
-
/**
|
|
4187
|
-
* @
|
|
4235
|
+
/** Play a music track that loops by default
|
|
4236
|
+
* @param {number} [volume] - Volume to play the music at
|
|
4237
|
+
* @param {boolean} [loop] - Should the music loop?
|
|
4238
|
+
* @param {boolean} [paused] - Should the music start paused
|
|
4239
|
+
* @return {SoundInstance} - The audio source node
|
|
4188
4240
|
*/
|
|
4189
|
-
|
|
4241
|
+
playMusic(volume=1, loop=true, paused=false)
|
|
4242
|
+
{ return this.play(undefined, volume, 1, 0, loop, paused); }
|
|
4190
4243
|
|
|
4191
|
-
/** Play the sound as a note with a semitone offset
|
|
4244
|
+
/** Play the sound as a musical note with a semitone offset
|
|
4245
|
+
* This can be used to play music with chromatic scales
|
|
4192
4246
|
* @param {number} semitoneOffset - How many semitones to offset pitch
|
|
4193
|
-
* @param {Vector2} [pos] - World space position to play the sound
|
|
4194
|
-
* @param {number} [volume=1] - How much to scale volume by
|
|
4195
|
-
* @return {
|
|
4247
|
+
* @param {Vector2} [pos] - World space position to play the sound if any
|
|
4248
|
+
* @param {number} [volume=1] - How much to scale volume by
|
|
4249
|
+
* @return {SoundInstance} - The audio source node
|
|
4196
4250
|
*/
|
|
4197
4251
|
playNote(semitoneOffset, pos, volume)
|
|
4198
|
-
{
|
|
4252
|
+
{
|
|
4253
|
+
const pitch = getNoteFrequency(semitoneOffset, 1);
|
|
4254
|
+
return this.play(pos, volume, pitch, 0);
|
|
4255
|
+
}
|
|
4199
4256
|
|
|
4200
4257
|
/** Get how long this sound is in seconds
|
|
4201
4258
|
* @return {number} - How long the sound is in seconds (undefined if loading)
|
|
4202
4259
|
*/
|
|
4203
|
-
getDuration()
|
|
4260
|
+
getDuration()
|
|
4204
4261
|
{ return this.sampleChannels && this.sampleChannels[0].length / this.sampleRate; }
|
|
4205
|
-
|
|
4206
|
-
/** Check if sound is
|
|
4207
|
-
* @return {boolean} - True if sound is
|
|
4262
|
+
|
|
4263
|
+
/** Check if sound is loaded, for sounds fetched from a url
|
|
4264
|
+
* @return {boolean} - True if sound is loaded and ready to play
|
|
4208
4265
|
*/
|
|
4209
|
-
|
|
4266
|
+
isLoaded() { return this.loadedPercent === 1; }
|
|
4210
4267
|
}
|
|
4211
4268
|
|
|
4212
4269
|
///////////////////////////////////////////////////////////////////////////////
|
|
4213
4270
|
|
|
4214
|
-
/**
|
|
4271
|
+
/**
|
|
4215
4272
|
* Sound Wave Object - Stores a wave sound for later use and can be played positionally
|
|
4216
4273
|
* - this can be used to play wave, mp3, and ogg files
|
|
4217
4274
|
* @example
|
|
4218
4275
|
* // create a sound
|
|
4219
4276
|
* const sound_example = new SoundWave('sound.mp3');
|
|
4220
|
-
*
|
|
4277
|
+
*
|
|
4221
4278
|
* // play the sound
|
|
4222
4279
|
* sound_example.play();
|
|
4223
4280
|
*/
|
|
@@ -4241,34 +4298,210 @@ class SoundWave extends Sound
|
|
|
4241
4298
|
this.loadSound(filename);
|
|
4242
4299
|
}
|
|
4243
4300
|
|
|
4244
|
-
/** Loads a sound from a URL and decodes it into sample data. Must be used with await!
|
|
4245
|
-
* @param {string} filename
|
|
4246
|
-
* @return {Promise<void>} */
|
|
4247
|
-
async loadSound(filename)
|
|
4301
|
+
/** Loads a sound from a URL and decodes it into sample data. Must be used with await!
|
|
4302
|
+
* @param {string} filename
|
|
4303
|
+
* @return {Promise<void>} */
|
|
4304
|
+
async loadSound(filename)
|
|
4305
|
+
{
|
|
4306
|
+
const response = await fetch(filename);
|
|
4307
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
4308
|
+
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
|
4309
|
+
|
|
4310
|
+
// convert audio buffer to sample channels across multiple frames
|
|
4311
|
+
const channelCount = audioBuffer.numberOfChannels;
|
|
4312
|
+
const samplesPerFrame = 1e5;
|
|
4313
|
+
const sampleChannels = [];
|
|
4314
|
+
for (let channel = 0; channel < channelCount; channel++)
|
|
4315
|
+
{
|
|
4316
|
+
const channelData = audioBuffer.getChannelData(channel);
|
|
4317
|
+
const channelLength = channelData.length;
|
|
4318
|
+
sampleChannels[channel] = new Array(channelLength);
|
|
4319
|
+
let sampleIndex = 0;
|
|
4320
|
+
while (sampleIndex < channelLength)
|
|
4321
|
+
{
|
|
4322
|
+
// yield to next frame
|
|
4323
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
4324
|
+
|
|
4325
|
+
// copy chunk of samples
|
|
4326
|
+
const endIndex = min(sampleIndex + samplesPerFrame, channelLength);
|
|
4327
|
+
for (; sampleIndex < endIndex; sampleIndex++)
|
|
4328
|
+
sampleChannels[channel][sampleIndex] = channelData[sampleIndex];
|
|
4329
|
+
|
|
4330
|
+
// update loaded percent
|
|
4331
|
+
const samplesTotal = channelCount * channelLength;
|
|
4332
|
+
const samplesProcessed = channel * channelLength + sampleIndex;
|
|
4333
|
+
this.loadedPercent = samplesProcessed / samplesTotal;
|
|
4334
|
+
}
|
|
4335
|
+
}
|
|
4336
|
+
|
|
4337
|
+
// setup the sound to be played
|
|
4338
|
+
this.sampleRate = audioBuffer.sampleRate;
|
|
4339
|
+
this.sampleChannels = sampleChannels;
|
|
4340
|
+
this.loadedPercent = 1;
|
|
4341
|
+
if (this.onloadCallback)
|
|
4342
|
+
this.onloadCallback();
|
|
4343
|
+
}
|
|
4344
|
+
}
|
|
4345
|
+
|
|
4346
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
4347
|
+
|
|
4348
|
+
/**
|
|
4349
|
+
* Sound Instance - Wraps an AudioBufferSourceNode for individual sound control
|
|
4350
|
+
* Represents a single playing instance of a sound with pause/resume capabilities
|
|
4351
|
+
* @example
|
|
4352
|
+
* // Play a sound and get an instance for control
|
|
4353
|
+
* const jumpSound = new Sound([.5,.5,220]);
|
|
4354
|
+
* const instance = jumpSound.play();
|
|
4355
|
+
*
|
|
4356
|
+
* // Control the individual instance
|
|
4357
|
+
* instance.setVolume(.5);
|
|
4358
|
+
* instance.pause();
|
|
4359
|
+
* instance.unpause();
|
|
4360
|
+
* instance.stop();
|
|
4361
|
+
*/
|
|
4362
|
+
class SoundInstance
|
|
4363
|
+
{
|
|
4364
|
+
/** Create a sound instance
|
|
4365
|
+
* @param {Sound} sound - The sound object
|
|
4366
|
+
* @param {number} [volume] - How much to scale volume by
|
|
4367
|
+
* @param {number} [rate] - The playback rate to use
|
|
4368
|
+
* @param {number} [pan] - How much to apply stereo panning
|
|
4369
|
+
* @param {boolean} [loop] - Should the sound loop?
|
|
4370
|
+
* @param {boolean} [paused] - Should the sound start paused? */
|
|
4371
|
+
constructor(sound, volume=1, rate=1, pan=0, loop=false, paused=false)
|
|
4372
|
+
{
|
|
4373
|
+
ASSERT(sound instanceof Sound, 'SoundInstance requires a valid Sound object');
|
|
4374
|
+
ASSERT(volume >= 0, 'Sound volume must be positive or zero');
|
|
4375
|
+
ASSERT(rate >= 0, 'Sound rate must be positive or zero');
|
|
4376
|
+
ASSERT(isNumber(pan), 'Sound pan must be a number');
|
|
4377
|
+
|
|
4378
|
+
/** @property {Sound} - The sound object */
|
|
4379
|
+
this.sound = sound;
|
|
4380
|
+
/** @property {number} - How much to scale volume by */
|
|
4381
|
+
this.volume = volume;
|
|
4382
|
+
/** @property {number} - The playback rate to use */
|
|
4383
|
+
this.rate = rate;
|
|
4384
|
+
/** @property {number} - How much to apply stereo panning */
|
|
4385
|
+
this.pan = pan;
|
|
4386
|
+
/** @property {boolean} - Should the sound loop */
|
|
4387
|
+
this.loop = loop;
|
|
4388
|
+
/** @property {number} - Timestamp for audio context when paused */
|
|
4389
|
+
this.pausedTime = 0;
|
|
4390
|
+
/** @property {number} - Timestamp for audio context when started */
|
|
4391
|
+
this.startTime = undefined;
|
|
4392
|
+
/** @property {GainNode} - Gain node for the sound */
|
|
4393
|
+
this.gainNode = undefined;
|
|
4394
|
+
/** @property {AudioBufferSourceNode} - Source node of the audio */
|
|
4395
|
+
this.source = undefined;
|
|
4396
|
+
// setup end callback and start sound
|
|
4397
|
+
this.onendedCallback = (source)=>
|
|
4398
|
+
{
|
|
4399
|
+
if (source === this.source)
|
|
4400
|
+
this.source = undefined;
|
|
4401
|
+
};
|
|
4402
|
+
if (!paused)
|
|
4403
|
+
this.start();
|
|
4404
|
+
}
|
|
4405
|
+
|
|
4406
|
+
/** Start playing the sound instance from the offset time
|
|
4407
|
+
* @param {number} [offset] - Offset in seconds to start playback from
|
|
4408
|
+
*/
|
|
4409
|
+
start(offset=0)
|
|
4410
|
+
{
|
|
4411
|
+
ASSERT(offset >= 0, 'Sound start offset must be positive or zero');
|
|
4412
|
+
if (this.isPlaying())
|
|
4413
|
+
this.stop();
|
|
4414
|
+
this.gainNode = audioContext.createGain();
|
|
4415
|
+
this.source = playSamples(this.sound.sampleChannels, this.volume, this.rate, this.pan, this.loop, this.sound.sampleRate, this.gainNode, offset, this.onendedCallback);
|
|
4416
|
+
this.startTime = audioContext.currentTime - offset;
|
|
4417
|
+
this.pausedTime = undefined;
|
|
4418
|
+
}
|
|
4419
|
+
|
|
4420
|
+
/** Set the volume of this sound instance
|
|
4421
|
+
* @param {number} volume */
|
|
4422
|
+
setVolume(volume)
|
|
4423
|
+
{
|
|
4424
|
+
ASSERT(volume >= 0, 'Sound volume must be positive or zero');
|
|
4425
|
+
this.volume = volume;
|
|
4426
|
+
if (this.gainNode)
|
|
4427
|
+
this.gainNode.gain.value = volume;
|
|
4428
|
+
}
|
|
4429
|
+
|
|
4430
|
+
/** Stop this sound instance and reset position to the start */
|
|
4431
|
+
stop(fadeTime=0)
|
|
4432
|
+
{
|
|
4433
|
+
ASSERT(fadeTime >= 0, 'Sound fade time must be positive or zero');
|
|
4434
|
+
if (this.isPlaying())
|
|
4435
|
+
{
|
|
4436
|
+
if (fadeTime)
|
|
4437
|
+
{
|
|
4438
|
+
// ramp off gain
|
|
4439
|
+
const startFade = audioContext.currentTime;
|
|
4440
|
+
const endFade = startFade + fadeTime;
|
|
4441
|
+
this.gainNode.gain.linearRampToValueAtTime(1, startFade);
|
|
4442
|
+
this.gainNode.gain.linearRampToValueAtTime(0, endFade);
|
|
4443
|
+
this.source.stop(endFade);
|
|
4444
|
+
}
|
|
4445
|
+
else
|
|
4446
|
+
this.source.stop();
|
|
4447
|
+
}
|
|
4448
|
+
this.pausedTime = 0;
|
|
4449
|
+
this.source = undefined;
|
|
4450
|
+
this.startTime = undefined;
|
|
4451
|
+
}
|
|
4452
|
+
|
|
4453
|
+
/** Pause this sound instance */
|
|
4454
|
+
pause()
|
|
4455
|
+
{
|
|
4456
|
+
if (this.isPaused())
|
|
4457
|
+
return;
|
|
4458
|
+
|
|
4459
|
+
// save current time and stop sound
|
|
4460
|
+
this.pausedTime = this.getCurrentTime();
|
|
4461
|
+
this.source.stop();
|
|
4462
|
+
this.source = undefined;
|
|
4463
|
+
this.startTime = undefined;
|
|
4464
|
+
}
|
|
4465
|
+
|
|
4466
|
+
/** Unpauses this sound instance */
|
|
4467
|
+
resume()
|
|
4468
|
+
{
|
|
4469
|
+
if (!this.isPaused())
|
|
4470
|
+
return;
|
|
4471
|
+
|
|
4472
|
+
// restart sound from paused time
|
|
4473
|
+
this.start(this.pausedTime);
|
|
4474
|
+
}
|
|
4475
|
+
|
|
4476
|
+
/** Check if this instance is currently playing
|
|
4477
|
+
* @return {boolean} - True if playing
|
|
4478
|
+
*/
|
|
4479
|
+
isPlaying() { return !!this.source; }
|
|
4480
|
+
|
|
4481
|
+
/** Check if this instance is paused and was not stopped
|
|
4482
|
+
* @return {boolean} - True if paused
|
|
4483
|
+
*/
|
|
4484
|
+
isPaused() { return !this.isPlaying(); }
|
|
4485
|
+
|
|
4486
|
+
/** Get the current playback time in seconds
|
|
4487
|
+
* @return {number} - Current playback time
|
|
4488
|
+
*/
|
|
4489
|
+
getCurrentTime()
|
|
4248
4490
|
{
|
|
4249
|
-
const
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
this.sampleChannels = [];
|
|
4253
|
-
for (let i = audioBuffer.numberOfChannels; i--;)
|
|
4254
|
-
this.sampleChannels[i] = Array.from(audioBuffer.getChannelData(i));
|
|
4255
|
-
this.sampleRate = audioBuffer.sampleRate;
|
|
4256
|
-
if (this.onloadCallback)
|
|
4257
|
-
this.onloadCallback();
|
|
4491
|
+
const deltaTime = mod(audioContext.currentTime - this.startTime,
|
|
4492
|
+
this.getDuration());
|
|
4493
|
+
return this.isPlaying() ? deltaTime : this.pausedTime;
|
|
4258
4494
|
}
|
|
4259
|
-
}
|
|
4260
4495
|
|
|
4261
|
-
/**
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
* @return {SoundWave} - The sound object for this file
|
|
4266
|
-
* @memberof Audio */
|
|
4267
|
-
function playAudioFile(filename, volume=1, loop=false)
|
|
4268
|
-
{
|
|
4269
|
-
if (!soundEnable || headlessMode) return;
|
|
4496
|
+
/** Get the total duration of this sound
|
|
4497
|
+
* @return {number} - Total duration in seconds
|
|
4498
|
+
*/
|
|
4499
|
+
getDuration() { return this.sound.getDuration() / this.rate; }
|
|
4270
4500
|
|
|
4271
|
-
|
|
4501
|
+
/** Get source of this sound instance
|
|
4502
|
+
* @return {AudioBufferSourceNode}
|
|
4503
|
+
*/
|
|
4504
|
+
getSource() { return this.source; }
|
|
4272
4505
|
}
|
|
4273
4506
|
|
|
4274
4507
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -4322,9 +4555,11 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
4322
4555
|
* @param {boolean} [loop] - True if the sound should loop when it reaches the end
|
|
4323
4556
|
* @param {number} [sampleRate=44100] - Sample rate for the sound
|
|
4324
4557
|
* @param {GainNode} [gainNode] - Optional gain node for volume control while playing
|
|
4558
|
+
* @param {number} [offset] - Offset in seconds to start playback from
|
|
4559
|
+
* @param {Function} [onended] - Callback for when the sound ends
|
|
4325
4560
|
* @return {AudioBufferSourceNode} - The audio node of the sound played
|
|
4326
4561
|
* @memberof Audio */
|
|
4327
|
-
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=
|
|
4562
|
+
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=audioDefaultSampleRate, gainNode, offset=0, onended)
|
|
4328
4563
|
{
|
|
4329
4564
|
if (!soundEnable || headlessMode) return;
|
|
4330
4565
|
|
|
@@ -4349,16 +4584,20 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
4349
4584
|
const pannerNode = new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)});
|
|
4350
4585
|
source.connect(pannerNode).connect(gainNode);
|
|
4351
4586
|
|
|
4352
|
-
//
|
|
4353
|
-
if (
|
|
4587
|
+
// callback when the sound ends
|
|
4588
|
+
if (onended)
|
|
4589
|
+
source.addEventListener('ended', ()=> onended(source));
|
|
4590
|
+
|
|
4591
|
+
if (!audioIsRunning())
|
|
4354
4592
|
{
|
|
4355
|
-
// fix stalled audio
|
|
4356
|
-
audioContext.resume()
|
|
4593
|
+
// fix stalled audio, this sound won't be able to play
|
|
4594
|
+
audioContext.resume();
|
|
4595
|
+
return;
|
|
4357
4596
|
}
|
|
4358
|
-
else
|
|
4359
|
-
source.start();
|
|
4360
4597
|
|
|
4361
|
-
// return sound
|
|
4598
|
+
// play and return sound
|
|
4599
|
+
const startOffset = offset * rate;
|
|
4600
|
+
source.start(0, startOffset);
|
|
4362
4601
|
return source;
|
|
4363
4602
|
}
|
|
4364
4603
|
|
|
@@ -4366,18 +4605,13 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
4366
4605
|
// ZzFXMicro - Zuper Zmall Zound Zynth - v1.3.2 by Frank Force
|
|
4367
4606
|
|
|
4368
4607
|
/** Generate and play a ZzFX sound
|
|
4369
|
-
*
|
|
4608
|
+
*
|
|
4370
4609
|
* <a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a>
|
|
4371
4610
|
* @param {Array} zzfxSound - Array of ZzFX parameters, ex. [.5,.5]
|
|
4372
4611
|
* @return {AudioBufferSourceNode} - The audio node of the sound played
|
|
4373
4612
|
* @memberof Audio */
|
|
4374
4613
|
function zzfx(...zzfxSound) { return playSamples([zzfxG(...zzfxSound)]); }
|
|
4375
4614
|
|
|
4376
|
-
/** Sample rate used for all ZzFX sounds
|
|
4377
|
-
* @default 44100
|
|
4378
|
-
* @memberof Audio */
|
|
4379
|
-
const zzfxR = 44100;
|
|
4380
|
-
|
|
4381
4615
|
/** Generate samples for a ZzFX sound
|
|
4382
4616
|
* @param {number} [volume] - Volume scale (percent)
|
|
4383
4617
|
* @param {number} [randomness] - How much to randomize frequency (percent Hz)
|
|
@@ -4401,11 +4635,10 @@ const zzfxR = 44100;
|
|
|
4401
4635
|
* @param {number} [tremolo] - Trembling effect, rate controlled by repeat time (percent)
|
|
4402
4636
|
* @param {number} [filter] - Filter cutoff frequency, positive for HPF, negative for LPF (Hz)
|
|
4403
4637
|
* @return {Array} - Array of audio samples
|
|
4404
|
-
* @memberof Audio
|
|
4405
|
-
*/
|
|
4638
|
+
* @memberof Audio */
|
|
4406
4639
|
function zzfxG
|
|
4407
4640
|
(
|
|
4408
|
-
volume = 1,
|
|
4641
|
+
volume = 1,
|
|
4409
4642
|
randomness = .05,
|
|
4410
4643
|
frequency = 220,
|
|
4411
4644
|
attack = 0,
|
|
@@ -4413,11 +4646,11 @@ function zzfxG
|
|
|
4413
4646
|
release = .1,
|
|
4414
4647
|
shape = 0,
|
|
4415
4648
|
shapeCurve = 1,
|
|
4416
|
-
slide = 0,
|
|
4417
|
-
deltaSlide = 0,
|
|
4418
|
-
pitchJump = 0,
|
|
4419
|
-
pitchJumpTime = 0,
|
|
4420
|
-
repeatTime = 0,
|
|
4649
|
+
slide = 0,
|
|
4650
|
+
deltaSlide = 0,
|
|
4651
|
+
pitchJump = 0,
|
|
4652
|
+
pitchJumpTime = 0,
|
|
4653
|
+
repeatTime = 0,
|
|
4421
4654
|
noise = 0,
|
|
4422
4655
|
modulation = 0,
|
|
4423
4656
|
bitCrush = 0,
|
|
@@ -4429,19 +4662,19 @@ function zzfxG
|
|
|
4429
4662
|
)
|
|
4430
4663
|
{
|
|
4431
4664
|
// init parameters
|
|
4432
|
-
let sampleRate =
|
|
4433
|
-
PI2 = PI*2,
|
|
4665
|
+
let sampleRate = audioDefaultSampleRate,
|
|
4666
|
+
PI2 = PI*2,
|
|
4434
4667
|
startSlide = slide *= 500 * PI2 / sampleRate / sampleRate,
|
|
4435
|
-
startFrequency = frequency *=
|
|
4668
|
+
startFrequency = frequency *=
|
|
4436
4669
|
(1 + rand(randomness,-randomness)) * PI2 / sampleRate,
|
|
4437
|
-
modOffset = 0, // modulation offset
|
|
4670
|
+
modOffset = 0, // modulation offset
|
|
4438
4671
|
repeat = 0, // repeat offset
|
|
4439
4672
|
crush = 0, // bit crush offset
|
|
4440
4673
|
jump = 1, // pitch jump timer
|
|
4441
4674
|
length, // sample length
|
|
4442
4675
|
b = [], // sample buffer
|
|
4443
4676
|
t = 0, // sample time
|
|
4444
|
-
i = 0, // sample index
|
|
4677
|
+
i = 0, // sample index
|
|
4445
4678
|
s = 0, // sample value
|
|
4446
4679
|
f, // wave frequency
|
|
4447
4680
|
|
|
@@ -4449,7 +4682,7 @@ function zzfxG
|
|
|
4449
4682
|
quality = 2, w = PI2 * abs(filter) * 2 / sampleRate,
|
|
4450
4683
|
cos = Math.cos(w), alpha = Math.sin(w) / 2 / quality,
|
|
4451
4684
|
a0 = 1 + alpha, a1 = -2*cos / a0, a2 = (1 - alpha) / a0,
|
|
4452
|
-
b0 = (1 + sign(filter) * cos) / 2 / a0,
|
|
4685
|
+
b0 = (1 + sign(filter) * cos) / 2 / a0,
|
|
4453
4686
|
b1 = -(sign(filter) + cos) / a0, b2 = b0,
|
|
4454
4687
|
x2 = 0, x1 = 0, y2 = 0, y1 = 0;
|
|
4455
4688
|
|
|
@@ -4495,7 +4728,7 @@ function zzfxG
|
|
|
4495
4728
|
0); // post release
|
|
4496
4729
|
|
|
4497
4730
|
s = delay ? s/2 + (delay > i ? 0 : // delay
|
|
4498
|
-
(i<length-delay? 1 : (length-i)/delay) * // release delay
|
|
4731
|
+
(i<length-delay? 1 : (length-i)/delay) * // release delay
|
|
4499
4732
|
b[i-delay|0]/2/volume) : s; // sample delay
|
|
4500
4733
|
|
|
4501
4734
|
if (filter) // apply filter
|
|
@@ -4507,14 +4740,14 @@ function zzfxG
|
|
|
4507
4740
|
t += f + f*noise*Math.sin(i**5); // noise
|
|
4508
4741
|
|
|
4509
4742
|
if (jump && ++jump > pitchJumpTime) // pitch jump
|
|
4510
|
-
{
|
|
4743
|
+
{
|
|
4511
4744
|
frequency += pitchJump; // apply pitch jump
|
|
4512
4745
|
startFrequency += pitchJump; // also apply to start
|
|
4513
4746
|
jump = 0; // stop pitch jump time
|
|
4514
|
-
}
|
|
4747
|
+
}
|
|
4515
4748
|
|
|
4516
4749
|
if (repeatTime && !(++repeat % repeatTime)) // repeat
|
|
4517
|
-
{
|
|
4750
|
+
{
|
|
4518
4751
|
frequency = startFrequency; // reset frequency
|
|
4519
4752
|
slide = startSlide; // reset slide
|
|
4520
4753
|
jump ||= 1; // reset pitch jump time
|
|
@@ -4523,7 +4756,7 @@ function zzfxG
|
|
|
4523
4756
|
|
|
4524
4757
|
return b; // return sample buffer
|
|
4525
4758
|
}
|
|
4526
|
-
/**
|
|
4759
|
+
/**
|
|
4527
4760
|
* LittleJS Tile Layer System
|
|
4528
4761
|
* - Caches arrays of tiles to off screen canvas for fast rendering
|
|
4529
4762
|
* - Unlimited numbers of layers, allocates canvases as needed
|
|
@@ -4537,9 +4770,9 @@ function zzfxG
|
|
|
4537
4770
|
// Tile Layer System
|
|
4538
4771
|
|
|
4539
4772
|
/** Keep track of all tile layers with collision
|
|
4540
|
-
* @type {Array<TileCollisionLayer>}
|
|
4773
|
+
* @type {Array<TileCollisionLayer>}
|
|
4541
4774
|
* @memberof TileCollision */
|
|
4542
|
-
|
|
4775
|
+
const tileCollisionLayers = [];
|
|
4543
4776
|
|
|
4544
4777
|
/** Get tile collision data for a given cell in the grid
|
|
4545
4778
|
* @param {Vector2} pos
|
|
@@ -4593,7 +4826,7 @@ function tileCollisionRaycast(posStart, posEnd, object, solidOnly=true)
|
|
|
4593
4826
|
}
|
|
4594
4827
|
|
|
4595
4828
|
///////////////////////////////////////////////////////////////////////////////
|
|
4596
|
-
/**
|
|
4829
|
+
/**
|
|
4597
4830
|
* Load tile layers from exported data
|
|
4598
4831
|
* @param {Object} tileMapData - Level data from exported data
|
|
4599
4832
|
* @param {TileInfo} [tileInfo] - Default tile info (used for size and texture)
|
|
@@ -4626,13 +4859,13 @@ function tileCollisionLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisio
|
|
|
4626
4859
|
{
|
|
4627
4860
|
const dataLayer = tileMapData.layers[layerIndex];
|
|
4628
4861
|
ASSERT(dataLayer.data && dataLayer.data.length);
|
|
4629
|
-
ASSERT(levelSize.area()
|
|
4862
|
+
ASSERT(levelSize.area() === dataLayer.data.length);
|
|
4630
4863
|
|
|
4631
4864
|
const layerRenderOrder = renderOrder - (layerCount - 1 - layerIndex);
|
|
4632
4865
|
const tileLayer = new TileCollisionLayer(vec2(), levelSize, tileInfo, layerRenderOrder);
|
|
4633
4866
|
tileLayers[layerIndex] = tileLayer;
|
|
4634
4867
|
|
|
4635
|
-
for (let x=levelSize.x; x--;)
|
|
4868
|
+
for (let x=levelSize.x; x--;)
|
|
4636
4869
|
for (let y=levelSize.y; y--;)
|
|
4637
4870
|
{
|
|
4638
4871
|
const pos = vec2(x, levelSize.y-1-y);
|
|
@@ -4691,7 +4924,7 @@ class TileLayerData
|
|
|
4691
4924
|
/**
|
|
4692
4925
|
* Canvas Layer - cached off screen rendering system
|
|
4693
4926
|
* - Contains an offscreen canvas that can be rendered to
|
|
4694
|
-
* -
|
|
4927
|
+
* - WebGL rendering is optional, call useWebGL to enable
|
|
4695
4928
|
* @extends EngineObject
|
|
4696
4929
|
* @example
|
|
4697
4930
|
* const canvasLayer = new CanvasLayer(vec2(), vec2(200,100));
|
|
@@ -4713,18 +4946,18 @@ class CanvasLayer extends EngineObject
|
|
|
4713
4946
|
this.canvas = headlessMode ? undefined : new OffscreenCanvas(canvasSize.x, canvasSize.y);
|
|
4714
4947
|
/** @property {OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this layer */
|
|
4715
4948
|
this.context = headlessMode ? undefined : this.canvas.getContext('2d');
|
|
4716
|
-
/** @property {WebGLTexture} - Texture if using
|
|
4949
|
+
/** @property {WebGLTexture} - Texture if using WebGL for this layer, call useWebGL to enable */
|
|
4717
4950
|
this.glTexture = undefined;
|
|
4718
4951
|
this.gravityScale = 0; // disable gravity by default for canvas layers
|
|
4719
4952
|
}
|
|
4720
|
-
|
|
4953
|
+
|
|
4721
4954
|
/** Destroy this canvas layer */
|
|
4722
4955
|
destroy()
|
|
4723
4956
|
{
|
|
4724
4957
|
if (this.destroyed)
|
|
4725
4958
|
return;
|
|
4726
4959
|
|
|
4727
|
-
// free up the
|
|
4960
|
+
// free up the WebGL texture
|
|
4728
4961
|
if (this.glTexture)
|
|
4729
4962
|
glDeleteTexture(this.glTexture);
|
|
4730
4963
|
super.destroy();
|
|
@@ -4749,12 +4982,12 @@ class CanvasLayer extends EngineObject
|
|
|
4749
4982
|
draw(pos, size, angle=0, color=WHITE, mirror=false, additiveColor, screenSpace=false, context)
|
|
4750
4983
|
{
|
|
4751
4984
|
// draw the canvas layer as a single tile that uses the whole texture
|
|
4752
|
-
const
|
|
4985
|
+
const useWebGL = glEnable && this.glTexture !== undefined;
|
|
4753
4986
|
const tileInfo = new TileInfo().setFullImage(this.canvas, this.glTexture);
|
|
4754
|
-
drawTile(pos, size, tileInfo, color, angle, mirror, additiveColor,
|
|
4987
|
+
drawTile(pos, size, tileInfo, color, angle, mirror, additiveColor, useWebGL, screenSpace, context);
|
|
4755
4988
|
}
|
|
4756
4989
|
|
|
4757
|
-
/** Draw onto the layer canvas in world space (bypass
|
|
4990
|
+
/** Draw onto the layer canvas in world space (bypass WebGL)
|
|
4758
4991
|
* @param {Vector2} pos
|
|
4759
4992
|
* @param {Vector2} size
|
|
4760
4993
|
* @param {number} angle
|
|
@@ -4788,8 +5021,8 @@ class CanvasLayer extends EngineObject
|
|
|
4788
5021
|
if (textureInfo)
|
|
4789
5022
|
{
|
|
4790
5023
|
context.globalAlpha = color.a; // only alpha is supported
|
|
4791
|
-
context.drawImage(textureInfo.image,
|
|
4792
|
-
tileInfo.pos.x, tileInfo.pos.y,
|
|
5024
|
+
context.drawImage(textureInfo.image,
|
|
5025
|
+
tileInfo.pos.x, tileInfo.pos.y,
|
|
4793
5026
|
tileInfo.size.x, tileInfo.size.y, -.5, -.5, 1, 1);
|
|
4794
5027
|
context.globalAlpha = 1;
|
|
4795
5028
|
}
|
|
@@ -4807,11 +5040,11 @@ class CanvasLayer extends EngineObject
|
|
|
4807
5040
|
* @param {Vector2} [size=(1,1)]
|
|
4808
5041
|
* @param {Color} [color=(1,1,1,1)]
|
|
4809
5042
|
* @param {number} [angle=0] */
|
|
4810
|
-
drawRect(pos, size, color, angle)
|
|
5043
|
+
drawRect(pos, size, color, angle)
|
|
4811
5044
|
{ this.drawTile(pos, size, undefined, color, angle); }
|
|
4812
5045
|
|
|
4813
|
-
/** Create or update the
|
|
4814
|
-
* @param {boolean} [enable] - enable
|
|
5046
|
+
/** Create or update the WebGL texture for this layer
|
|
5047
|
+
* @param {boolean} [enable] - enable WebGL rendering and update the texture */
|
|
4815
5048
|
useWebGL(enable=true)
|
|
4816
5049
|
{
|
|
4817
5050
|
if (glEnable && enable)
|
|
@@ -4857,7 +5090,7 @@ class TileLayer extends CanvasLayer
|
|
|
4857
5090
|
this.canvas = new OffscreenCanvas(canvasSize.x, canvasSize.y);
|
|
4858
5091
|
/** @property {OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
|
|
4859
5092
|
this.context = this.canvas.getContext('2d');
|
|
4860
|
-
/** @property {WebGLTexture} - Texture if using
|
|
5093
|
+
/** @property {WebGLTexture} - Texture if using WebGL for this layer */
|
|
4861
5094
|
this.glTexture = useWebGL ? glCreateTexture(this.canvas) : undefined;
|
|
4862
5095
|
// set no friction by default, applied friction is max of both objects
|
|
4863
5096
|
this.friction = 0;
|
|
@@ -4882,7 +5115,7 @@ class TileLayer extends CanvasLayer
|
|
|
4882
5115
|
}
|
|
4883
5116
|
}
|
|
4884
5117
|
|
|
4885
|
-
/** Set data at a given position in the array
|
|
5118
|
+
/** Set data at a given position in the array
|
|
4886
5119
|
* @param {Vector2} layerPos - Local position in array
|
|
4887
5120
|
* @param {TileLayerData} data - Data to set
|
|
4888
5121
|
* @param {boolean} [redraw] - Force the tile to redraw if true */
|
|
@@ -4894,26 +5127,26 @@ class TileLayer extends CanvasLayer
|
|
|
4894
5127
|
redraw && this.drawTileData(layerPos);
|
|
4895
5128
|
}
|
|
4896
5129
|
}
|
|
4897
|
-
|
|
4898
|
-
/** Get data at a given position in the array
|
|
5130
|
+
|
|
5131
|
+
/** Get data at a given position in the array
|
|
4899
5132
|
* @param {Vector2} layerPos - Local position in array
|
|
4900
5133
|
* @return {TileLayerData} */
|
|
4901
5134
|
getData(layerPos)
|
|
4902
5135
|
{ return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0]; }
|
|
4903
|
-
|
|
5136
|
+
|
|
4904
5137
|
// Render the tile layer, called automatically by the engine
|
|
4905
5138
|
render()
|
|
4906
5139
|
{
|
|
4907
|
-
ASSERT(drawContext
|
|
4908
|
-
|
|
5140
|
+
ASSERT(drawContext !== this.context, 'must call redrawEnd() after drawing tiles!');
|
|
5141
|
+
|
|
4909
5142
|
// draw the tile layer as a single tile
|
|
4910
5143
|
const tileInfo = new TileInfo().setFullImage(this.canvas, this.glTexture);
|
|
4911
5144
|
const pos = this.pos.add(this.size.scale(.5));
|
|
4912
|
-
const
|
|
4913
|
-
drawTile(pos, this.size, tileInfo, WHITE, 0, false, CLEAR_BLACK,
|
|
5145
|
+
const useWebGL = glEnable && this.glTexture !== undefined;
|
|
5146
|
+
drawTile(pos, this.size, tileInfo, WHITE, 0, false, CLEAR_BLACK, useWebGL);
|
|
4914
5147
|
}
|
|
4915
5148
|
|
|
4916
|
-
/** Draw all the tile data to an offscreen canvas
|
|
5149
|
+
/** Draw all the tile data to an offscreen canvas
|
|
4917
5150
|
* - This may be slow in some browsers but only needs to be done once */
|
|
4918
5151
|
redraw()
|
|
4919
5152
|
{
|
|
@@ -4923,7 +5156,7 @@ class TileLayer extends CanvasLayer
|
|
|
4923
5156
|
this.drawTileData(vec2(x,y), false);
|
|
4924
5157
|
this.redrawEnd();
|
|
4925
5158
|
if (this.glTexture)
|
|
4926
|
-
this.useWebGL(); // update
|
|
5159
|
+
this.useWebGL(); // update WebGL texture
|
|
4927
5160
|
}
|
|
4928
5161
|
|
|
4929
5162
|
/** Call to start the redraw process
|
|
@@ -4959,7 +5192,7 @@ class TileLayer extends CanvasLayer
|
|
|
4959
5192
|
/** Call to end the redraw process */
|
|
4960
5193
|
redrawEnd()
|
|
4961
5194
|
{
|
|
4962
|
-
ASSERT(drawContext
|
|
5195
|
+
ASSERT(drawContext === this.context, 'must call redrawStart() before drawing tiles');
|
|
4963
5196
|
glCopyToContext(drawContext);
|
|
4964
5197
|
//debugSaveCanvas(this.canvas);
|
|
4965
5198
|
|
|
@@ -4970,7 +5203,7 @@ class TileLayer extends CanvasLayer
|
|
|
4970
5203
|
/** Draw the tile at a given position in the tile grid
|
|
4971
5204
|
* This can be used to clear out tiles when they are destroyed
|
|
4972
5205
|
* Tiles can also be redrawn if inside a redrawStart/End block
|
|
4973
|
-
* @param {Vector2} layerPos
|
|
5206
|
+
* @param {Vector2} layerPos
|
|
4974
5207
|
* @param {boolean} [clear] - should the old tile be cleared out
|
|
4975
5208
|
*/
|
|
4976
5209
|
drawTileData(layerPos, clear=true)
|
|
@@ -4985,9 +5218,9 @@ class TileLayer extends CanvasLayer
|
|
|
4985
5218
|
|
|
4986
5219
|
// draw the tile if it has layer data
|
|
4987
5220
|
const d = this.getData(layerPos);
|
|
4988
|
-
if (d.tile
|
|
5221
|
+
if (d.tile !== undefined)
|
|
4989
5222
|
{
|
|
4990
|
-
ASSERT(drawContext
|
|
5223
|
+
ASSERT(drawContext === this.context, 'must call redrawStart() before drawing tiles');
|
|
4991
5224
|
const pos = layerPos.add(vec2(.5));
|
|
4992
5225
|
const tileInfo = tile(d.tile, s, this.tileInfo.textureIndex, this.tileInfo.padding);
|
|
4993
5226
|
drawTile(pos, vec2(1), tileInfo, d.color, d.direction*PI/2, d.mirror);
|
|
@@ -5000,7 +5233,7 @@ class TileLayer extends CanvasLayer
|
|
|
5000
5233
|
* Tile Collision Layer - a tile layer with collision
|
|
5001
5234
|
* - adds collision data and functions to TileLayer
|
|
5002
5235
|
* - there can be multiple tile collision layers
|
|
5003
|
-
* - tile
|
|
5236
|
+
* - tile collision layers should not overlap each other
|
|
5004
5237
|
* @extends TileLayer
|
|
5005
5238
|
*/
|
|
5006
5239
|
class TileCollisionLayer extends TileLayer
|
|
@@ -5151,7 +5384,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
5151
5384
|
debugRaycast && debugLine(posStart, posEnd, '#00f', .02);
|
|
5152
5385
|
}
|
|
5153
5386
|
}
|
|
5154
|
-
/**
|
|
5387
|
+
/**
|
|
5155
5388
|
* LittleJS Particle System
|
|
5156
5389
|
*/
|
|
5157
5390
|
|
|
@@ -5168,7 +5401,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
5168
5401
|
* rgb(1,1,1,1), rgb(0,0,0,1), // colorStartA, colorStartB
|
|
5169
5402
|
* rgb(1,1,1,0), rgb(0,0,0,0), // colorEndA, colorEndB
|
|
5170
5403
|
* 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
|
|
5171
|
-
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
|
|
5404
|
+
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
|
|
5172
5405
|
* .5, 1 // randomness, collide, additive, randomColorLinear, renderOrder
|
|
5173
5406
|
* );
|
|
5174
5407
|
*/
|
|
@@ -5176,35 +5409,35 @@ class ParticleEmitter extends EngineObject
|
|
|
5176
5409
|
{
|
|
5177
5410
|
/** Create a particle system with the given settings
|
|
5178
5411
|
* @param {Vector2} position - World space position of the emitter
|
|
5179
|
-
* @param {
|
|
5180
|
-
* @param {
|
|
5181
|
-
* @param {
|
|
5182
|
-
* @param {
|
|
5183
|
-
* @param {
|
|
5412
|
+
* @param {number} [angle] - Angle to emit the particles
|
|
5413
|
+
* @param {number|Vector2} [emitSize] - World space size of the emitter (float for circle diameter, vec2 for rect)
|
|
5414
|
+
* @param {number} [emitTime] - How long to stay alive (0 is forever)
|
|
5415
|
+
* @param {number} [emitRate] - How many particles per second to spawn, does not emit if 0
|
|
5416
|
+
* @param {number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter
|
|
5184
5417
|
* @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
|
|
5185
5418
|
* @param {Color} [colorStartA=(1,1,1,1)] - Color at start of life 1, randomized between start colors
|
|
5186
5419
|
* @param {Color} [colorStartB=(1,1,1,1)] - Color at start of life 2, randomized between start colors
|
|
5187
5420
|
* @param {Color} [colorEndA=(1,1,1,0)] - Color at end of life 1, randomized between end colors
|
|
5188
5421
|
* @param {Color} [colorEndB=(1,1,1,0)] - Color at end of life 2, randomized between end colors
|
|
5189
|
-
* @param {
|
|
5190
|
-
* @param {
|
|
5191
|
-
* @param {
|
|
5192
|
-
* @param {
|
|
5193
|
-
* @param {
|
|
5194
|
-
* @param {
|
|
5195
|
-
* @param {
|
|
5196
|
-
* @param {
|
|
5197
|
-
* @param {
|
|
5198
|
-
* @param {
|
|
5199
|
-
* @param {
|
|
5422
|
+
* @param {number} [particleTime] - How long particles live
|
|
5423
|
+
* @param {number} [sizeStart] - How big are particles at start
|
|
5424
|
+
* @param {number} [sizeEnd] - How big are particles at end
|
|
5425
|
+
* @param {number} [speed] - How fast are particles when spawned
|
|
5426
|
+
* @param {number} [angleSpeed] - How fast are particles rotating
|
|
5427
|
+
* @param {number} [damping] - How much to dampen particle speed
|
|
5428
|
+
* @param {number} [angleDamping] - How much to dampen particle angular speed
|
|
5429
|
+
* @param {number} [gravityScale] - How much gravity effect particles
|
|
5430
|
+
* @param {number} [particleConeAngle] - Cone for start particle angle
|
|
5431
|
+
* @param {number} [fadeRate] - How quick to fade particles at start/end in percent of life
|
|
5432
|
+
* @param {number} [randomness] - Apply extra randomness percent
|
|
5200
5433
|
* @param {boolean} [collideTiles] - Do particles collide against tiles
|
|
5201
5434
|
* @param {boolean} [additive] - Should particles use additive blend
|
|
5202
5435
|
* @param {boolean} [randomColorLinear] - Should color be randomized linearly or across each component
|
|
5203
|
-
* @param {
|
|
5436
|
+
* @param {number} [renderOrder] - Render order for particles (additive is above other stuff by default)
|
|
5204
5437
|
* @param {boolean} [localSpace] - Should it be in local space of emitter (world space is default)
|
|
5205
5438
|
*/
|
|
5206
5439
|
constructor
|
|
5207
|
-
(
|
|
5440
|
+
(
|
|
5208
5441
|
position,
|
|
5209
5442
|
angle,
|
|
5210
5443
|
emitSize = 0,
|
|
@@ -5226,7 +5459,7 @@ class ParticleEmitter extends EngineObject
|
|
|
5226
5459
|
gravityScale = 0,
|
|
5227
5460
|
particleConeAngle = PI,
|
|
5228
5461
|
fadeRate = .1,
|
|
5229
|
-
randomness = .2,
|
|
5462
|
+
randomness = .2,
|
|
5230
5463
|
collideTiles = false,
|
|
5231
5464
|
additive = false,
|
|
5232
5465
|
randomColorLinear = true,
|
|
@@ -5237,13 +5470,13 @@ class ParticleEmitter extends EngineObject
|
|
|
5237
5470
|
super(position, vec2(), tileInfo, angle, undefined, renderOrder);
|
|
5238
5471
|
|
|
5239
5472
|
// emitter settings
|
|
5240
|
-
/** @property {
|
|
5473
|
+
/** @property {number|Vector2} - World space size of the emitter (float for circle diameter, vec2 for rect) */
|
|
5241
5474
|
this.emitSize = emitSize
|
|
5242
|
-
/** @property {
|
|
5475
|
+
/** @property {number} - How long to stay alive (0 is forever) */
|
|
5243
5476
|
this.emitTime = emitTime;
|
|
5244
|
-
/** @property {
|
|
5477
|
+
/** @property {number} - How many particles per second to spawn, does not emit if 0 */
|
|
5245
5478
|
this.emitRate = emitRate;
|
|
5246
|
-
/** @property {
|
|
5479
|
+
/** @property {number} - Local angle to apply velocity to particles from emitter */
|
|
5247
5480
|
this.emitConeAngle = emitConeAngle;
|
|
5248
5481
|
|
|
5249
5482
|
// color settings
|
|
@@ -5259,27 +5492,27 @@ class ParticleEmitter extends EngineObject
|
|
|
5259
5492
|
this.randomColorLinear = randomColorLinear;
|
|
5260
5493
|
|
|
5261
5494
|
// particle settings
|
|
5262
|
-
/** @property {
|
|
5495
|
+
/** @property {number} - How long particles live */
|
|
5263
5496
|
this.particleTime = particleTime;
|
|
5264
|
-
/** @property {
|
|
5497
|
+
/** @property {number} - How big are particles at start */
|
|
5265
5498
|
this.sizeStart = sizeStart;
|
|
5266
|
-
/** @property {
|
|
5499
|
+
/** @property {number} - How big are particles at end */
|
|
5267
5500
|
this.sizeEnd = sizeEnd;
|
|
5268
|
-
/** @property {
|
|
5501
|
+
/** @property {number} - How fast are particles when spawned */
|
|
5269
5502
|
this.speed = speed;
|
|
5270
|
-
/** @property {
|
|
5503
|
+
/** @property {number} - How fast are particles rotating */
|
|
5271
5504
|
this.angleSpeed = angleSpeed;
|
|
5272
|
-
/** @property {
|
|
5505
|
+
/** @property {number} - How much to dampen particle speed */
|
|
5273
5506
|
this.damping = damping;
|
|
5274
|
-
/** @property {
|
|
5507
|
+
/** @property {number} - How much to dampen particle angular speed */
|
|
5275
5508
|
this.angleDamping = angleDamping;
|
|
5276
|
-
/** @property {
|
|
5509
|
+
/** @property {number} - How much gravity affects particles */
|
|
5277
5510
|
this.gravityScale = gravityScale;
|
|
5278
|
-
/** @property {
|
|
5511
|
+
/** @property {number} - Cone for start particle angle */
|
|
5279
5512
|
this.particleConeAngle = particleConeAngle;
|
|
5280
|
-
/** @property {
|
|
5513
|
+
/** @property {number} - How quick to fade in particles at start/end in percent of life */
|
|
5281
5514
|
this.fadeRate = fadeRate;
|
|
5282
|
-
/** @property {
|
|
5515
|
+
/** @property {number} - Apply extra randomness percent */
|
|
5283
5516
|
this.randomness = randomness;
|
|
5284
5517
|
/** @property {boolean} - Do particles collide against tiles */
|
|
5285
5518
|
this.collideTiles = collideTiles;
|
|
@@ -5287,16 +5520,16 @@ class ParticleEmitter extends EngineObject
|
|
|
5287
5520
|
this.additive = additive;
|
|
5288
5521
|
/** @property {boolean} - Should it be in local space of emitter */
|
|
5289
5522
|
this.localSpace = localSpace;
|
|
5290
|
-
/** @property {
|
|
5523
|
+
/** @property {number} - If non zero the particle is drawn as a trail, stretched in the direction of velocity */
|
|
5291
5524
|
this.trailScale = 0;
|
|
5292
5525
|
/** @property {Function} - Callback when particle is destroyed */
|
|
5293
5526
|
this.particleDestroyCallback = undefined;
|
|
5294
5527
|
/** @property {Function} - Callback when particle is created */
|
|
5295
5528
|
this.particleCreateCallback = undefined;
|
|
5296
|
-
/** @property {
|
|
5529
|
+
/** @property {number} - Track particle emit time */
|
|
5297
5530
|
this.emitTimeBuffer = 0;
|
|
5298
5531
|
}
|
|
5299
|
-
|
|
5532
|
+
|
|
5300
5533
|
/** Update the emitter to spawn particles, called automatically by engine once each frame */
|
|
5301
5534
|
update()
|
|
5302
5535
|
{
|
|
@@ -5320,7 +5553,7 @@ class ParticleEmitter extends EngineObject
|
|
|
5320
5553
|
if (debugParticles)
|
|
5321
5554
|
{
|
|
5322
5555
|
// show emitter bounds
|
|
5323
|
-
const emitSize = typeof this.emitSize
|
|
5556
|
+
const emitSize = typeof this.emitSize === 'number' ? vec2(this.emitSize) : this.emitSize;
|
|
5324
5557
|
debugRect(this.pos, emitSize, '#0f0', 0, this.angle);
|
|
5325
5558
|
}
|
|
5326
5559
|
}
|
|
@@ -5330,7 +5563,7 @@ class ParticleEmitter extends EngineObject
|
|
|
5330
5563
|
emitParticle()
|
|
5331
5564
|
{
|
|
5332
5565
|
// spawn a particle
|
|
5333
|
-
let pos = typeof this.emitSize
|
|
5566
|
+
let pos = typeof this.emitSize === 'number' ? // check if number was used
|
|
5334
5567
|
randInCircle(this.emitSize/2) // circle emitter
|
|
5335
5568
|
: vec2(rand(-.5,.5), rand(-.5,.5)) // box emitter
|
|
5336
5569
|
.multiply(this.emitSize).rotate(this.angle)
|
|
@@ -5355,7 +5588,7 @@ class ParticleEmitter extends EngineObject
|
|
|
5355
5588
|
const colorStart = randColor(this.colorStartA, this.colorStartB, this.randomColorLinear);
|
|
5356
5589
|
const colorEnd = randColor(this.colorEndA, this.colorEndB, this.randomColorLinear);
|
|
5357
5590
|
const velocityAngle = this.localSpace ? coneAngle : this.angle + coneAngle;
|
|
5358
|
-
|
|
5591
|
+
|
|
5359
5592
|
// build particle
|
|
5360
5593
|
const particle = new Particle(pos, this.tileInfo, angle, colorStart, colorEnd, particleTime, sizeStart, sizeEnd, this.fadeRate, this.additive, this.trailScale, this.localSpace && this, this.particleDestroyCallback);
|
|
5361
5594
|
particle.velocity = vec2().setAngle(velocityAngle, speed);
|
|
@@ -5400,38 +5633,38 @@ class Particle extends EngineObject
|
|
|
5400
5633
|
* Typically this is created automatically by a ParticleEmitter
|
|
5401
5634
|
* @param {Vector2} position - World space position of the particle
|
|
5402
5635
|
* @param {TileInfo} tileInfo - Tile info to render particles
|
|
5403
|
-
* @param {
|
|
5636
|
+
* @param {number} angle - Angle to rotate the particle
|
|
5404
5637
|
* @param {Color} colorStart - Color at start of life
|
|
5405
5638
|
* @param {Color} colorEnd - Color at end of life
|
|
5406
|
-
* @param {
|
|
5407
|
-
* @param {
|
|
5408
|
-
* @param {
|
|
5409
|
-
* @param {
|
|
5639
|
+
* @param {number} lifeTime - How long to live for
|
|
5640
|
+
* @param {number} sizeStart - Size at start of life
|
|
5641
|
+
* @param {number} sizeEnd - Size at end of life
|
|
5642
|
+
* @param {number} fadeRate - How quick to fade in/out
|
|
5410
5643
|
* @param {boolean} additive - Does it use additive blend mode
|
|
5411
|
-
* @param {
|
|
5644
|
+
* @param {number} trailScale - If a trail, how long to make it
|
|
5412
5645
|
* @param {ParticleEmitter} [localSpaceEmitter] - Parent emitter if local space
|
|
5413
5646
|
* @param {Function} [destroyCallback] - Callback when particle dies
|
|
5414
5647
|
*/
|
|
5415
5648
|
constructor(position, tileInfo, angle, colorStart, colorEnd, lifeTime, sizeStart, sizeEnd, fadeRate, additive, trailScale, localSpaceEmitter, destroyCallback
|
|
5416
5649
|
)
|
|
5417
|
-
{
|
|
5418
|
-
super(position, vec2(), tileInfo, angle);
|
|
5419
|
-
|
|
5650
|
+
{
|
|
5651
|
+
super(position, vec2(), tileInfo, angle);
|
|
5652
|
+
|
|
5420
5653
|
/** @property {Color} - Color at start of life */
|
|
5421
5654
|
this.colorStart = colorStart;
|
|
5422
5655
|
/** @property {Color} - Calculated change in color */
|
|
5423
5656
|
this.colorEndDelta = colorEnd.subtract(colorStart);
|
|
5424
|
-
/** @property {
|
|
5657
|
+
/** @property {number} - How long to live for */
|
|
5425
5658
|
this.lifeTime = lifeTime;
|
|
5426
|
-
/** @property {
|
|
5659
|
+
/** @property {number} - Size at start of life */
|
|
5427
5660
|
this.sizeStart = sizeStart;
|
|
5428
|
-
/** @property {
|
|
5661
|
+
/** @property {number} - Calculated change in size */
|
|
5429
5662
|
this.sizeEndDelta = sizeEnd - sizeStart;
|
|
5430
|
-
/** @property {
|
|
5663
|
+
/** @property {number} - How quick to fade in/out */
|
|
5431
5664
|
this.fadeRate = fadeRate;
|
|
5432
5665
|
/** @property {boolean} - Is it additive */
|
|
5433
5666
|
this.additive = additive;
|
|
5434
|
-
/** @property {
|
|
5667
|
+
/** @property {number} - If a trail, how long to make it */
|
|
5435
5668
|
this.trailScale = trailScale;
|
|
5436
5669
|
/** @property {ParticleEmitter} - Parent emitter if local space */
|
|
5437
5670
|
this.localSpaceEmitter = localSpaceEmitter;
|
|
@@ -5471,7 +5704,7 @@ class Particle extends EngineObject
|
|
|
5471
5704
|
this.colorStart.r + p * this.colorEndDelta.r,
|
|
5472
5705
|
this.colorStart.g + p * this.colorEndDelta.g,
|
|
5473
5706
|
this.colorStart.b + p * this.colorEndDelta.b,
|
|
5474
|
-
(this.colorStart.a + p * this.colorEndDelta.a) *
|
|
5707
|
+
(this.colorStart.a + p * this.colorEndDelta.a) *
|
|
5475
5708
|
(p < fadeRate ? p/fadeRate : p > 1-fadeRate ? (1-p)/fadeRate : 1)); // fade alpha
|
|
5476
5709
|
|
|
5477
5710
|
// draw the particle
|
|
@@ -5481,7 +5714,7 @@ class Particle extends EngineObject
|
|
|
5481
5714
|
if (this.localSpaceEmitter)
|
|
5482
5715
|
{
|
|
5483
5716
|
// in local space of emitter
|
|
5484
|
-
pos = this.localSpaceEmitter.pos.add(pos.rotate(-this.localSpaceEmitter.angle));
|
|
5717
|
+
pos = this.localSpaceEmitter.pos.add(pos.rotate(-this.localSpaceEmitter.angle));
|
|
5485
5718
|
angle += this.localSpaceEmitter.angle;
|
|
5486
5719
|
}
|
|
5487
5720
|
if (this.trailScale)
|
|
@@ -5505,7 +5738,7 @@ class Particle extends EngineObject
|
|
|
5505
5738
|
this.additive && setBlendMode();
|
|
5506
5739
|
debugParticles && debugRect(pos, size, '#f005', 0, angle);
|
|
5507
5740
|
|
|
5508
|
-
if (p
|
|
5741
|
+
if (p === 1)
|
|
5509
5742
|
{
|
|
5510
5743
|
// destroy particle when it's time runs out
|
|
5511
5744
|
this.color = color;
|
|
@@ -5515,7 +5748,7 @@ class Particle extends EngineObject
|
|
|
5515
5748
|
}
|
|
5516
5749
|
}
|
|
5517
5750
|
}
|
|
5518
|
-
/**
|
|
5751
|
+
/**
|
|
5519
5752
|
* LittleJS Medal System
|
|
5520
5753
|
* - Tracks and displays medals
|
|
5521
5754
|
* - Saves medals to local storage
|
|
@@ -5536,7 +5769,7 @@ let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimeLast;
|
|
|
5536
5769
|
/** Initialize medals with a save name used for storage
|
|
5537
5770
|
* - Call this after creating all medals
|
|
5538
5771
|
* - Checks if medals are unlocked
|
|
5539
|
-
* @param {
|
|
5772
|
+
* @param {string} saveName
|
|
5540
5773
|
* @memberof Medals */
|
|
5541
5774
|
function medalsInit(saveName)
|
|
5542
5775
|
{
|
|
@@ -5551,7 +5784,7 @@ function medalsInit(saveName)
|
|
|
5551
5784
|
{
|
|
5552
5785
|
if (!medalsDisplayQueue.length)
|
|
5553
5786
|
return;
|
|
5554
|
-
|
|
5787
|
+
|
|
5555
5788
|
// update first medal in queue
|
|
5556
5789
|
const medal = medalsDisplayQueue[0];
|
|
5557
5790
|
const time = timeReal - medalsDisplayTimeLast;
|
|
@@ -5566,7 +5799,7 @@ function medalsInit(saveName)
|
|
|
5566
5799
|
{
|
|
5567
5800
|
// slide on/off medals
|
|
5568
5801
|
const slideOffTime = medalDisplayTime - medalDisplaySlideTime;
|
|
5569
|
-
const hidePercent =
|
|
5802
|
+
const hidePercent =
|
|
5570
5803
|
time < medalDisplaySlideTime ? 1 - time / medalDisplaySlideTime :
|
|
5571
5804
|
time > slideOffTime ? (time - slideOffTime) / medalDisplaySlideTime : 0;
|
|
5572
5805
|
medal.render(hidePercent);
|
|
@@ -5582,43 +5815,43 @@ function medalsForEach(callback)
|
|
|
5582
5815
|
|
|
5583
5816
|
///////////////////////////////////////////////////////////////////////////////
|
|
5584
5817
|
|
|
5585
|
-
/**
|
|
5586
|
-
* Medal - Tracks an unlockable medal
|
|
5818
|
+
/**
|
|
5819
|
+
* Medal - Tracks an unlockable medal
|
|
5587
5820
|
* @example
|
|
5588
5821
|
* // create a medal
|
|
5589
5822
|
* const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️');
|
|
5590
|
-
*
|
|
5823
|
+
*
|
|
5591
5824
|
* // initialize medals
|
|
5592
5825
|
* medalsInit('Example Game');
|
|
5593
|
-
*
|
|
5826
|
+
*
|
|
5594
5827
|
* // unlock the medal
|
|
5595
5828
|
* medal_example.unlock();
|
|
5596
5829
|
*/
|
|
5597
5830
|
class Medal
|
|
5598
5831
|
{
|
|
5599
5832
|
/** Create a medal object and adds it to the list of medals
|
|
5600
|
-
* @param {
|
|
5601
|
-
* @param {
|
|
5602
|
-
* @param {
|
|
5603
|
-
* @param {
|
|
5604
|
-
* @param {
|
|
5833
|
+
* @param {number} id - The unique identifier of the medal
|
|
5834
|
+
* @param {string} name - Name of the medal
|
|
5835
|
+
* @param {string} [description] - Description of the medal
|
|
5836
|
+
* @param {string} [icon] - Icon for the medal
|
|
5837
|
+
* @param {string} [src] - Image location for the medal
|
|
5605
5838
|
*/
|
|
5606
5839
|
constructor(id, name, description='', icon='🏆', src)
|
|
5607
5840
|
{
|
|
5608
5841
|
ASSERT(id >= 0 && !medals[id]);
|
|
5609
|
-
|
|
5610
|
-
/** @property {
|
|
5842
|
+
|
|
5843
|
+
/** @property {number} - The unique identifier of the medal */
|
|
5611
5844
|
this.id = id;
|
|
5612
|
-
|
|
5613
|
-
/** @property {
|
|
5845
|
+
|
|
5846
|
+
/** @property {string} - Name of the medal */
|
|
5614
5847
|
this.name = name;
|
|
5615
|
-
|
|
5616
|
-
/** @property {
|
|
5848
|
+
|
|
5849
|
+
/** @property {string} - Description of the medal */
|
|
5617
5850
|
this.description = description;
|
|
5618
|
-
|
|
5619
|
-
/** @property {
|
|
5851
|
+
|
|
5852
|
+
/** @property {string} - Icon for the medal */
|
|
5620
5853
|
this.icon = icon;
|
|
5621
|
-
|
|
5854
|
+
|
|
5622
5855
|
/** @property {boolean} - Is the medal unlocked? */
|
|
5623
5856
|
this.unlocked = false;
|
|
5624
5857
|
|
|
@@ -5643,7 +5876,7 @@ class Medal
|
|
|
5643
5876
|
}
|
|
5644
5877
|
|
|
5645
5878
|
/** Render a medal
|
|
5646
|
-
* @param {
|
|
5879
|
+
* @param {number} [hidePercent] - How much to slide the medal off screen
|
|
5647
5880
|
*/
|
|
5648
5881
|
render(hidePercent=0)
|
|
5649
5882
|
{
|
|
@@ -5684,7 +5917,7 @@ class Medal
|
|
|
5684
5917
|
|
|
5685
5918
|
/** Render the icon for a medal
|
|
5686
5919
|
* @param {Vector2} pos - Screen space position
|
|
5687
|
-
* @param {
|
|
5920
|
+
* @param {number} size - Screen space size
|
|
5688
5921
|
*/
|
|
5689
5922
|
renderIcon(pos, size)
|
|
5690
5923
|
{
|
|
@@ -5694,14 +5927,14 @@ class Medal
|
|
|
5694
5927
|
else
|
|
5695
5928
|
drawTextScreen(this.icon, pos, size*.7, BLACK);
|
|
5696
5929
|
}
|
|
5697
|
-
|
|
5930
|
+
|
|
5698
5931
|
// Get local storage key used by the medal
|
|
5699
5932
|
storageKey() { return medalsSaveName + '_' + this.id; }
|
|
5700
5933
|
}
|
|
5701
5934
|
/**
|
|
5702
5935
|
* LittleJS WebGL Interface
|
|
5703
|
-
* - All
|
|
5704
|
-
* - Will fall back to 2D canvas rendering if
|
|
5936
|
+
* - All WebGL used by the engine is wrapped up here
|
|
5937
|
+
* - Will fall back to 2D canvas rendering if WebGL is not supported
|
|
5705
5938
|
* - For normal stuff you won't need to see or call anything in this file
|
|
5706
5939
|
* - For advanced stuff there are helper functions to create shaders, textures, etc
|
|
5707
5940
|
* - Can be disabled with glEnable to revert to 2D canvas rendering
|
|
@@ -5716,24 +5949,27 @@ class Medal
|
|
|
5716
5949
|
* @memberof WebGL */
|
|
5717
5950
|
let glCanvas;
|
|
5718
5951
|
|
|
5719
|
-
/**
|
|
5952
|
+
/** WebGL2 context for `glCanvas`
|
|
5720
5953
|
* @type {WebGL2RenderingContext}
|
|
5721
5954
|
* @memberof WebGL */
|
|
5722
5955
|
let glContext;
|
|
5723
5956
|
|
|
5724
|
-
/** Should
|
|
5957
|
+
/** Should WebGL be setup with anti-aliasing? must be set before calling engineInit
|
|
5725
5958
|
* @type {boolean}
|
|
5726
5959
|
* @memberof WebGL */
|
|
5727
5960
|
let glAntialias = true;
|
|
5728
5961
|
|
|
5729
5962
|
// WebGL internal variables not exposed to documentation
|
|
5730
|
-
let glShader, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData,
|
|
5963
|
+
let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount;
|
|
5731
5964
|
|
|
5732
|
-
// WebGL internal constants
|
|
5733
|
-
const
|
|
5965
|
+
// WebGL internal constants
|
|
5966
|
+
const gl_ARRAY_BUFFER_SIZE = 4e5;
|
|
5734
5967
|
const gl_INDICES_PER_INSTANCE = 11;
|
|
5735
5968
|
const gl_INSTANCE_BYTE_STRIDE = gl_INDICES_PER_INSTANCE * 4;
|
|
5736
|
-
const
|
|
5969
|
+
const gl_MAX_INSTANCES = gl_ARRAY_BUFFER_SIZE / gl_INSTANCE_BYTE_STRIDE | 0;
|
|
5970
|
+
const gl_INDICES_PER_POLY_VERTEX = 3;
|
|
5971
|
+
const gl_POLY_VERTEX_BYTE_STRIDE = gl_INDICES_PER_POLY_VERTEX * 4;
|
|
5972
|
+
const gl_MAX_POLY_VERTEXES = gl_ARRAY_BUFFER_SIZE / gl_POLY_VERTEX_BYTE_STRIDE | 0;
|
|
5737
5973
|
|
|
5738
5974
|
///////////////////////////////////////////////////////////////////////////////
|
|
5739
5975
|
|
|
@@ -5754,11 +5990,11 @@ function glInit()
|
|
|
5754
5990
|
return;
|
|
5755
5991
|
}
|
|
5756
5992
|
|
|
5757
|
-
// create the
|
|
5993
|
+
// create the WebGL canvas
|
|
5758
5994
|
const rootElement = mainCanvas.parentElement;
|
|
5759
5995
|
rootElement.appendChild(glCanvas);
|
|
5760
5996
|
|
|
5761
|
-
// setup
|
|
5997
|
+
// setup instanced rendering shader program
|
|
5762
5998
|
glShader = glCreateProgram(
|
|
5763
5999
|
'#version 300 es\n' + // specify GLSL ES version
|
|
5764
6000
|
'precision highp float;'+ // use highp for better accuracy
|
|
@@ -5786,40 +6022,59 @@ function glInit()
|
|
|
5786
6022
|
'}' // end of shader
|
|
5787
6023
|
);
|
|
5788
6024
|
|
|
6025
|
+
// setup poly rendering shaders
|
|
6026
|
+
glPolyShader = glCreateProgram(
|
|
6027
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
6028
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
6029
|
+
'uniform mat4 m;'+ // transform matrix
|
|
6030
|
+
'in vec2 p;'+ // in: position
|
|
6031
|
+
'in vec4 c;'+ // in: color
|
|
6032
|
+
'out vec4 d;'+ // out: color
|
|
6033
|
+
'void main(){'+ // shader entry point
|
|
6034
|
+
'gl_Position=m*vec4(p,1,1);'+ // transform position
|
|
6035
|
+
'd=c;'+ // pass color to fragment shader
|
|
6036
|
+
'}' // end of shader
|
|
6037
|
+
,
|
|
6038
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
6039
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
6040
|
+
'in vec4 d;'+ // in: color
|
|
6041
|
+
'out vec4 c;'+ // out: color
|
|
6042
|
+
'void main(){'+ // shader entry point
|
|
6043
|
+
'c=d;'+ // set color
|
|
6044
|
+
'}' // end of shader
|
|
6045
|
+
);
|
|
6046
|
+
|
|
5789
6047
|
// init buffers
|
|
5790
|
-
const glInstanceData = new ArrayBuffer(
|
|
6048
|
+
const glInstanceData = new ArrayBuffer(gl_ARRAY_BUFFER_SIZE);
|
|
5791
6049
|
glPositionData = new Float32Array(glInstanceData);
|
|
5792
6050
|
glColorData = new Uint32Array(glInstanceData);
|
|
5793
6051
|
glArrayBuffer = glContext.createBuffer();
|
|
5794
6052
|
glGeometryBuffer = glContext.createBuffer();
|
|
5795
6053
|
|
|
5796
6054
|
// create the geometry buffer, triangle strip square
|
|
5797
|
-
const geometry = new Float32Array([
|
|
6055
|
+
const geometry = new Float32Array([glBatchCount=0,0,1,0,0,1,1,1]);
|
|
5798
6056
|
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
5799
6057
|
glContext.bufferData(glContext.ARRAY_BUFFER, geometry, glContext.STATIC_DRAW);
|
|
5800
6058
|
}
|
|
5801
6059
|
|
|
5802
|
-
|
|
5803
|
-
// Also used by tile layer rendering when redrawing tiles
|
|
5804
|
-
function glPreRender()
|
|
6060
|
+
function glSetInstancedMode(force=false)
|
|
5805
6061
|
{
|
|
5806
|
-
if (!
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
6062
|
+
if (!glPolyMode && !force)
|
|
6063
|
+
return;
|
|
6064
|
+
|
|
6065
|
+
// setup instanced mode
|
|
6066
|
+
glFlush();
|
|
6067
|
+
glPolyMode = false;
|
|
5810
6068
|
glContext.useProgram(glShader);
|
|
5811
|
-
glContext.activeTexture(glContext.TEXTURE0);
|
|
5812
|
-
if (textureInfos[0])
|
|
5813
|
-
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
|
|
5814
6069
|
|
|
5815
6070
|
// set vertex attributes
|
|
5816
|
-
let offset =
|
|
6071
|
+
let offset = 0;
|
|
5817
6072
|
const initVertexAttribArray = (name, type, typeSize, size)=>
|
|
5818
6073
|
{
|
|
5819
6074
|
const location = glContext.getAttribLocation(glShader, name);
|
|
5820
6075
|
const stride = typeSize && gl_INSTANCE_BYTE_STRIDE; // only if not geometry
|
|
5821
6076
|
const divisor = typeSize && 1; // only if not geometry
|
|
5822
|
-
const normalize = typeSize
|
|
6077
|
+
const normalize = typeSize === 1; // only if color
|
|
5823
6078
|
glContext.enableVertexAttribArray(location);
|
|
5824
6079
|
glContext.vertexAttribPointer(location, size, type, normalize, stride, offset);
|
|
5825
6080
|
glContext.vertexAttribDivisor(location, divisor);
|
|
@@ -5828,26 +6083,84 @@ function glPreRender()
|
|
|
5828
6083
|
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
5829
6084
|
initVertexAttribArray('g', glContext.FLOAT, 0, 2); // geometry
|
|
5830
6085
|
glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
|
|
5831
|
-
glContext.bufferData(glContext.ARRAY_BUFFER,
|
|
6086
|
+
glContext.bufferData(glContext.ARRAY_BUFFER, gl_ARRAY_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
|
|
5832
6087
|
initVertexAttribArray('p', glContext.FLOAT, 4, 4); // position & size
|
|
5833
6088
|
initVertexAttribArray('u', glContext.FLOAT, 4, 4); // texture coords
|
|
5834
6089
|
initVertexAttribArray('c', glContext.UNSIGNED_BYTE, 1, 4); // color
|
|
5835
6090
|
initVertexAttribArray('a', glContext.UNSIGNED_BYTE, 1, 4); // additiveColor
|
|
5836
6091
|
initVertexAttribArray('r', glContext.FLOAT, 4, 1); // rotation
|
|
6092
|
+
}
|
|
6093
|
+
|
|
6094
|
+
function glSetPolyMode()
|
|
6095
|
+
{
|
|
6096
|
+
if (glPolyMode)
|
|
6097
|
+
return;
|
|
5837
6098
|
|
|
6099
|
+
// setup poly mode
|
|
6100
|
+
glFlush();
|
|
6101
|
+
glPolyMode = true;
|
|
6102
|
+
glContext.useProgram(glPolyShader);
|
|
6103
|
+
|
|
6104
|
+
// set vertex attributes
|
|
6105
|
+
let offset = 0;
|
|
6106
|
+
const initVertexAttribArray = (name, type, typeSize, size)=>
|
|
6107
|
+
{
|
|
6108
|
+
const location = glContext.getAttribLocation(glPolyShader, name);
|
|
6109
|
+
const normalize = typeSize === 1; // only normalize if color
|
|
6110
|
+
const stride = gl_POLY_VERTEX_BYTE_STRIDE;
|
|
6111
|
+
glContext.enableVertexAttribArray(location);
|
|
6112
|
+
glContext.vertexAttribPointer(location, size, type, normalize, stride, offset);
|
|
6113
|
+
glContext.vertexAttribDivisor(location, 0);
|
|
6114
|
+
offset += size*typeSize;
|
|
6115
|
+
}
|
|
6116
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
|
|
6117
|
+
glContext.bufferData(glContext.ARRAY_BUFFER, gl_ARRAY_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
|
|
6118
|
+
initVertexAttribArray('p', glContext.FLOAT, 4, 2); // position
|
|
6119
|
+
initVertexAttribArray('c', glContext.UNSIGNED_BYTE, 1, 4); // color
|
|
6120
|
+
}
|
|
6121
|
+
|
|
6122
|
+
// Setup WebGL render each frame, called automatically by engine
|
|
6123
|
+
// Also used by tile layer rendering when redrawing tiles
|
|
6124
|
+
function glPreRender()
|
|
6125
|
+
{
|
|
6126
|
+
if (!glEnable || !glContext) return;
|
|
6127
|
+
|
|
6128
|
+
// clear the canvas
|
|
6129
|
+
glClearCanvas();
|
|
6130
|
+
|
|
5838
6131
|
// build the transform matrix
|
|
5839
6132
|
const s = vec2(2*cameraScale).divide(mainCanvasSize);
|
|
5840
6133
|
const rotatedCam = cameraPos.rotate(-cameraAngle);
|
|
5841
6134
|
const p = vec2(-1).subtract(rotatedCam.multiply(s));
|
|
5842
6135
|
const ca = Math.cos(cameraAngle);
|
|
5843
6136
|
const sa = Math.sin(cameraAngle);
|
|
5844
|
-
|
|
5845
|
-
[
|
|
6137
|
+
const transform = [
|
|
5846
6138
|
s.x * ca, s.y * sa, 0, 0,
|
|
5847
6139
|
-s.x * sa, s.y * ca, 0, 0,
|
|
5848
6140
|
1, 1, 1, 0,
|
|
5849
|
-
p.x, p.y, 0, 1
|
|
5850
|
-
|
|
6141
|
+
p.x, p.y, 0, 1];
|
|
6142
|
+
|
|
6143
|
+
// set the same matrix for both shaders
|
|
6144
|
+
const initUniform = (program, uniform, value) =>
|
|
6145
|
+
{
|
|
6146
|
+
glContext.useProgram(program);
|
|
6147
|
+
const location = glContext.getUniformLocation(program, uniform);
|
|
6148
|
+
glContext.uniformMatrix4fv(location, false, value);
|
|
6149
|
+
}
|
|
6150
|
+
initUniform(glPolyShader, 'm', transform);
|
|
6151
|
+
initUniform(glShader, 'm', transform);
|
|
6152
|
+
|
|
6153
|
+
// set the active texture
|
|
6154
|
+
glContext.activeTexture(glContext.TEXTURE0);
|
|
6155
|
+
if (textureInfos[0])
|
|
6156
|
+
{
|
|
6157
|
+
glActiveTexture = textureInfos[0].glTexture;
|
|
6158
|
+
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
|
|
6159
|
+
}
|
|
6160
|
+
|
|
6161
|
+
// start in instanced rendering mode with additive blending off
|
|
6162
|
+
glAdditive = glBatchAdditive = glPolyMode = false;
|
|
6163
|
+
glSetInstancedMode(true);
|
|
5851
6164
|
}
|
|
5852
6165
|
|
|
5853
6166
|
/** Clear the canvas and setup the viewport
|
|
@@ -5855,21 +6168,21 @@ function glPreRender()
|
|
|
5855
6168
|
function glClearCanvas()
|
|
5856
6169
|
{
|
|
5857
6170
|
if (!glContext) return;
|
|
5858
|
-
|
|
6171
|
+
|
|
5859
6172
|
// clear and set to same size as main canvas
|
|
5860
6173
|
glContext.viewport(0, 0, glCanvas.width=drawCanvas.width, glCanvas.height=drawCanvas.height);
|
|
5861
6174
|
glContext.clear(glContext.COLOR_BUFFER_BIT);
|
|
5862
6175
|
}
|
|
5863
6176
|
|
|
5864
|
-
/** Set the
|
|
6177
|
+
/** Set the WebGL texture, called automatically if using multiple textures
|
|
5865
6178
|
* - This may also flush the gl buffer resulting in more draw calls and worse performance
|
|
5866
6179
|
* @param {WebGLTexture} texture
|
|
5867
|
-
* @param {boolean} wrap - Should the texture wrap or clamp
|
|
6180
|
+
* @param {boolean} [wrap] - Should the texture wrap or clamp
|
|
5868
6181
|
* @memberof WebGL */
|
|
5869
6182
|
function glSetTexture(texture, wrap=false)
|
|
5870
6183
|
{
|
|
5871
6184
|
// must flush cache with the old texture to set a new one
|
|
5872
|
-
if (!glContext || texture
|
|
6185
|
+
if (!glContext || texture === glActiveTexture)
|
|
5873
6186
|
return;
|
|
5874
6187
|
|
|
5875
6188
|
glFlush();
|
|
@@ -5882,8 +6195,8 @@ function glSetTexture(texture, wrap=false)
|
|
|
5882
6195
|
}
|
|
5883
6196
|
|
|
5884
6197
|
/** Compile WebGL shader of the given type, will throw errors if in debug mode
|
|
5885
|
-
* @param {
|
|
5886
|
-
* @param {
|
|
6198
|
+
* @param {string} source
|
|
6199
|
+
* @param {number} type
|
|
5887
6200
|
* @return {WebGLShader}
|
|
5888
6201
|
* @memberof WebGL */
|
|
5889
6202
|
function glCompileShader(source, type)
|
|
@@ -5902,8 +6215,8 @@ function glCompileShader(source, type)
|
|
|
5902
6215
|
}
|
|
5903
6216
|
|
|
5904
6217
|
/** Create WebGL program with given shaders
|
|
5905
|
-
* @param {
|
|
5906
|
-
* @param {
|
|
6218
|
+
* @param {string} vsSource
|
|
6219
|
+
* @param {string} fsSource
|
|
5907
6220
|
* @return {WebGLProgram}
|
|
5908
6221
|
* @memberof WebGL */
|
|
5909
6222
|
function glCreateProgram(vsSource, fsSource)
|
|
@@ -5951,7 +6264,7 @@ function glCreateTexture(image)
|
|
|
5951
6264
|
const whitePixel = new Uint8Array([255, 255, 255, 255]);
|
|
5952
6265
|
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, 1, 1, 0, glContext.RGBA, glContext.UNSIGNED_BYTE, whitePixel);
|
|
5953
6266
|
}
|
|
5954
|
-
|
|
6267
|
+
|
|
5955
6268
|
// set texture filtering
|
|
5956
6269
|
const filter = tilesPixelated ? glContext.NEAREST : glContext.LINEAR;
|
|
5957
6270
|
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, filter);
|
|
@@ -5959,7 +6272,6 @@ function glCreateTexture(image)
|
|
|
5959
6272
|
return texture;
|
|
5960
6273
|
}
|
|
5961
6274
|
|
|
5962
|
-
|
|
5963
6275
|
/** Deletes a WebGL texture
|
|
5964
6276
|
* @param {WebGLTexture} [texture]
|
|
5965
6277
|
* @memberof WebGL */
|
|
@@ -5987,18 +6299,23 @@ function glSetTextureData(texture, image)
|
|
|
5987
6299
|
* @memberof WebGL */
|
|
5988
6300
|
function glFlush()
|
|
5989
6301
|
{
|
|
5990
|
-
if (
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6302
|
+
if (glEnable && glContext && glBatchCount)
|
|
6303
|
+
{
|
|
6304
|
+
// set bend mode
|
|
6305
|
+
const destBlend = glBatchAdditive ? glContext.ONE : glContext.ONE_MINUS_SRC_ALPHA;
|
|
6306
|
+
glContext.blendFuncSeparate(glContext.SRC_ALPHA, destBlend, glContext.ONE, destBlend);
|
|
6307
|
+
glContext.enable(glContext.BLEND);
|
|
6308
|
+
glContext.bufferSubData(glContext.ARRAY_BUFFER, 0, glPositionData);
|
|
6309
|
+
|
|
6310
|
+
// draw the batch
|
|
6311
|
+
if (glPolyMode)
|
|
6312
|
+
glContext.drawArrays(glContext.TRIANGLE_STRIP, 0, glBatchCount);
|
|
6313
|
+
else
|
|
6314
|
+
glContext.drawArraysInstanced(glContext.TRIANGLE_STRIP, 0, 4, glBatchCount);
|
|
6315
|
+
if (debug || showWatermark)
|
|
6316
|
+
drawCount += glBatchCount;
|
|
6317
|
+
glBatchCount = 0;
|
|
6318
|
+
}
|
|
6002
6319
|
glBatchAdditive = glAdditive;
|
|
6003
6320
|
}
|
|
6004
6321
|
|
|
@@ -6014,7 +6331,8 @@ function glCopyToContext(context)
|
|
|
6014
6331
|
context.drawImage(glCanvas, 0, 0);
|
|
6015
6332
|
}
|
|
6016
6333
|
|
|
6017
|
-
/** Set anti-aliasing for
|
|
6334
|
+
/** Set anti-aliasing for WebGL canvas
|
|
6335
|
+
* Must be called before engineInit
|
|
6018
6336
|
* @param {boolean} [antialias]
|
|
6019
6337
|
* @memberof WebGL */
|
|
6020
6338
|
function glSetAntialias(antialias=true)
|
|
@@ -6024,25 +6342,27 @@ function glSetAntialias(antialias=true)
|
|
|
6024
6342
|
}
|
|
6025
6343
|
|
|
6026
6344
|
/** Add a sprite to the gl draw list, used by all gl draw functions
|
|
6027
|
-
* @param {
|
|
6028
|
-
* @param {
|
|
6029
|
-
* @param {
|
|
6030
|
-
* @param {
|
|
6031
|
-
* @param {
|
|
6032
|
-
* @param {
|
|
6033
|
-
* @param {
|
|
6034
|
-
* @param {
|
|
6035
|
-
* @param {
|
|
6036
|
-
* @param {
|
|
6037
|
-
* @param {
|
|
6345
|
+
* @param {number} x
|
|
6346
|
+
* @param {number} y
|
|
6347
|
+
* @param {number} sizeX
|
|
6348
|
+
* @param {number} sizeY
|
|
6349
|
+
* @param {number} [angle]
|
|
6350
|
+
* @param {number} [uv0X]
|
|
6351
|
+
* @param {number} [uv0Y]
|
|
6352
|
+
* @param {number} [uv1X]
|
|
6353
|
+
* @param {number} [uv1Y]
|
|
6354
|
+
* @param {number} [rgba=-1] - white is -1
|
|
6355
|
+
* @param {number} [rgbaAdditive=0] - black is 0
|
|
6038
6356
|
* @memberof WebGL */
|
|
6039
6357
|
function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgba=-1, rgbaAdditive=0)
|
|
6040
6358
|
{
|
|
6041
6359
|
// flush if there is not enough room or if different blend mode
|
|
6042
|
-
if (
|
|
6360
|
+
if (glBatchCount >= gl_MAX_INSTANCES || glBatchAdditive !== glAdditive)
|
|
6043
6361
|
glFlush();
|
|
6362
|
+
glSetInstancedMode();
|
|
6044
6363
|
|
|
6045
|
-
|
|
6364
|
+
glPolyMode = false;
|
|
6365
|
+
let offset = glBatchCount++ * gl_INDICES_PER_INSTANCE;
|
|
6046
6366
|
glPositionData[offset++] = x;
|
|
6047
6367
|
glPositionData[offset++] = y;
|
|
6048
6368
|
glPositionData[offset++] = sizeX;
|
|
@@ -6054,6 +6374,301 @@ function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgb
|
|
|
6054
6374
|
glColorData[offset++] = rgba;
|
|
6055
6375
|
glColorData[offset++] = rgbaAdditive;
|
|
6056
6376
|
glPositionData[offset++] = angle;
|
|
6377
|
+
}
|
|
6378
|
+
|
|
6379
|
+
/** Transform and add a polygon to the gl draw list
|
|
6380
|
+
* @param {Array} points - Array of Vector2 points
|
|
6381
|
+
* @param {number} rgba - Color of the polygon as a 32-bit integer
|
|
6382
|
+
* @param {number} x
|
|
6383
|
+
* @param {number} y
|
|
6384
|
+
* @param {number} sx
|
|
6385
|
+
* @param {number} sy
|
|
6386
|
+
* @param {number} angle
|
|
6387
|
+
* @param {boolean} [tristrip] - should tristrip algorithm be used
|
|
6388
|
+
* @memberof WebGL */
|
|
6389
|
+
function glDrawPointsTransform(points, rgba, x, y, sx, sy, angle, tristrip=true)
|
|
6390
|
+
{
|
|
6391
|
+
const pointsOut = [];
|
|
6392
|
+
for (const p of points)
|
|
6393
|
+
{
|
|
6394
|
+
// transform the point
|
|
6395
|
+
const px = p.x*sx;
|
|
6396
|
+
const py = p.y*sy;
|
|
6397
|
+
const sa = Math.sin(-angle);
|
|
6398
|
+
const ca = Math.cos(-angle);
|
|
6399
|
+
pointsOut.push(vec2(x + ca*px - sa*py, y + sa*px + ca*py));
|
|
6400
|
+
}
|
|
6401
|
+
const drawPoints = tristrip ? glPolyStrip(pointsOut) : pointsOut;
|
|
6402
|
+
glDrawPoints(drawPoints, rgba);
|
|
6403
|
+
}
|
|
6404
|
+
|
|
6405
|
+
/** Transform and add a polygon to the gl draw list
|
|
6406
|
+
* @param {Array} points - Array of Vector2 points
|
|
6407
|
+
* @param {number} rgba - Color of the polygon as a 32-bit integer
|
|
6408
|
+
* @param {number} lineWidth - Width of the outline
|
|
6409
|
+
* @param {number} x
|
|
6410
|
+
* @param {number} y
|
|
6411
|
+
* @param {number} sx
|
|
6412
|
+
* @param {number} sy
|
|
6413
|
+
* @param {number} angle
|
|
6414
|
+
* @memberof WebGL */
|
|
6415
|
+
function glDrawOutlineTransform(points, rgba, lineWidth, x, y, sx, sy, angle)
|
|
6416
|
+
{
|
|
6417
|
+
const outlinePoints = glMakeOutline(points, lineWidth);
|
|
6418
|
+
glDrawPointsTransform(outlinePoints, rgba, x, y, sx, sy, angle, false);
|
|
6419
|
+
}
|
|
6420
|
+
|
|
6421
|
+
/** Add a polygon to the gl draw list
|
|
6422
|
+
* @param {Array} points - Array of Vector2 points in triangle strip order
|
|
6423
|
+
* @param {number} rgba - Color of the polygon as a 32-bit integer
|
|
6424
|
+
* @memberof WebGL */
|
|
6425
|
+
function glDrawPoints(points, rgba)
|
|
6426
|
+
{
|
|
6427
|
+
if (!glEnable || points.length < 3)
|
|
6428
|
+
return; // needs at least 3 points to have area
|
|
6429
|
+
|
|
6430
|
+
// add 2 degenerate verts if batching with existing polys to separate them
|
|
6431
|
+
const needsBridge = glPolyMode && glBatchCount > 0;
|
|
6432
|
+
const bridgeVerts = needsBridge ? 2 : 0;
|
|
6433
|
+
const vertCount = points.length + bridgeVerts;
|
|
6434
|
+
|
|
6435
|
+
// flush if there is not enough room or if different blend mode
|
|
6436
|
+
if (!glPolyMode || glBatchCount+vertCount >= gl_MAX_POLY_VERTEXES || glBatchAdditive !== glAdditive)
|
|
6437
|
+
glFlush();
|
|
6438
|
+
glSetPolyMode();
|
|
6439
|
+
|
|
6440
|
+
let offset = glBatchCount * gl_INDICES_PER_POLY_VERTEX;
|
|
6441
|
+
|
|
6442
|
+
// add degenerate bridge if needed (repeat last vertex of previous poly, then first of new poly)
|
|
6443
|
+
if (needsBridge)
|
|
6444
|
+
{
|
|
6445
|
+
// repeat last vertex from previous batch (it's at offset - 3)
|
|
6446
|
+
const prevOffset = offset - 3;
|
|
6447
|
+
glPositionData[offset++] = glPositionData[prevOffset];
|
|
6448
|
+
glPositionData[offset++] = glPositionData[prevOffset + 1];
|
|
6449
|
+
glColorData[offset++] = glColorData[prevOffset + 2];
|
|
6450
|
+
|
|
6451
|
+
// repeat first vertex of new poly
|
|
6452
|
+
glPositionData[offset++] = points[0].x;
|
|
6453
|
+
glPositionData[offset++] = points[0].y;
|
|
6454
|
+
glColorData[offset++] = rgba;
|
|
6455
|
+
}
|
|
6456
|
+
|
|
6457
|
+
// write vertices - they're already in triangle strip order
|
|
6458
|
+
for (const point of points)
|
|
6459
|
+
{
|
|
6460
|
+
glPositionData[offset++] = point.x;
|
|
6461
|
+
glPositionData[offset++] = point.y;
|
|
6462
|
+
glColorData[offset++] = rgba;
|
|
6463
|
+
}
|
|
6464
|
+
glBatchCount += vertCount;
|
|
6465
|
+
}
|
|
6466
|
+
|
|
6467
|
+
// WebGL internal function to convert polygon to outline triangle strip
|
|
6468
|
+
function glMakeOutline(points, width)
|
|
6469
|
+
{
|
|
6470
|
+
if (points.length < 2)
|
|
6471
|
+
return [];
|
|
6472
|
+
|
|
6473
|
+
const halfWidth = width / 2;
|
|
6474
|
+
const strip = [];
|
|
6475
|
+
const n = points.length;
|
|
6476
|
+
const e = 1e-6;
|
|
6477
|
+
for (let i = 0; i < n; i++)
|
|
6478
|
+
{
|
|
6479
|
+
// for each vertex, calculate normal based on adjacent edges
|
|
6480
|
+
const prev = points[(i - 1 + n) % n];
|
|
6481
|
+
const curr = points[i];
|
|
6482
|
+
const next = points[(i + 1) % n];
|
|
6483
|
+
|
|
6484
|
+
// direction from previous to current
|
|
6485
|
+
const dx1 = curr.x - prev.x;
|
|
6486
|
+
const dy1 = curr.y - prev.y;
|
|
6487
|
+
const len1 = (dx1*dx1 + dy1*dy1)**.5;
|
|
6488
|
+
|
|
6489
|
+
// direction from current to next
|
|
6490
|
+
const dx2 = next.x - curr.x;
|
|
6491
|
+
const dy2 = next.y - curr.y;
|
|
6492
|
+
const len2 = (dx2*dx2 + dy2*dy2)**.5;
|
|
6493
|
+
|
|
6494
|
+
if (len1 < e && len2 < e)
|
|
6495
|
+
continue; // skip degenerate point
|
|
6496
|
+
|
|
6497
|
+
// calculate perpendicular normals for each edge
|
|
6498
|
+
const nx1 = len1 > e ? -dy1 / len1 : 0;
|
|
6499
|
+
const ny1 = len1 > e ? dx1 / len1 : 0;
|
|
6500
|
+
const nx2 = len2 > e ? -dy2 / len2 : 0;
|
|
6501
|
+
const ny2 = len2 > e ? dx2 / len2 : 0;
|
|
6502
|
+
|
|
6503
|
+
// average the normals for miter
|
|
6504
|
+
let nx = nx1 + nx2;
|
|
6505
|
+
let ny = ny1 + ny2;
|
|
6506
|
+
const nlen = (nx*nx + ny*ny)**.5;
|
|
6507
|
+
if (nlen < e)
|
|
6508
|
+
{
|
|
6509
|
+
// 180 degree turn - use perpendicular
|
|
6510
|
+
nx = nx1;
|
|
6511
|
+
ny = ny1;
|
|
6512
|
+
}
|
|
6513
|
+
else
|
|
6514
|
+
{
|
|
6515
|
+
// calculate miter length
|
|
6516
|
+
nx /= nlen;
|
|
6517
|
+
ny /= nlen;
|
|
6518
|
+
const dot = nx1 * nx + ny1 * ny;
|
|
6519
|
+
if (dot > e)
|
|
6520
|
+
{
|
|
6521
|
+
// scale normal by miter length
|
|
6522
|
+
const miterLength = 1 / dot;
|
|
6523
|
+
nx *= miterLength;
|
|
6524
|
+
ny *= miterLength;
|
|
6525
|
+
}
|
|
6526
|
+
}
|
|
6527
|
+
|
|
6528
|
+
// create inner and outer points along the normal
|
|
6529
|
+
const inner = vec2(curr.x - nx * halfWidth, curr.y - ny * halfWidth);
|
|
6530
|
+
const outer = vec2(curr.x + nx * halfWidth, curr.y + ny * halfWidth);
|
|
6531
|
+
strip.push(inner);
|
|
6532
|
+
strip.push(outer);
|
|
6533
|
+
}
|
|
6534
|
+
if (strip.length > 1)
|
|
6535
|
+
{
|
|
6536
|
+
// close the loop
|
|
6537
|
+
strip.push(strip[0]);
|
|
6538
|
+
strip.push(strip[1]);
|
|
6539
|
+
}
|
|
6540
|
+
return strip;
|
|
6541
|
+
}
|
|
6542
|
+
|
|
6543
|
+
// WebGL internal function to convert polys to tri strips
|
|
6544
|
+
function glPolyStrip(points)
|
|
6545
|
+
{
|
|
6546
|
+
// validate input
|
|
6547
|
+
if (points.length < 3)
|
|
6548
|
+
return [];
|
|
6549
|
+
|
|
6550
|
+
// cross product helper: (b-a) x (c-a)
|
|
6551
|
+
const cross = (a,b,c)=> (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
|
|
6552
|
+
|
|
6553
|
+
// calculate signed area of polygon
|
|
6554
|
+
const signedArea = (poly)=>
|
|
6555
|
+
{
|
|
6556
|
+
let area = 0;
|
|
6557
|
+
for (let i = poly.length; i--;)
|
|
6558
|
+
{
|
|
6559
|
+
const j = (i+1) % poly.length;
|
|
6560
|
+
area += poly[i].cross(poly[j]);
|
|
6561
|
+
}
|
|
6562
|
+
return area;
|
|
6563
|
+
}
|
|
6564
|
+
|
|
6565
|
+
// ensure counter-clockwise winding
|
|
6566
|
+
if (signedArea(points) < 0)
|
|
6567
|
+
points = points.reverse();
|
|
6568
|
+
|
|
6569
|
+
// tolerance constants
|
|
6570
|
+
const e = 1e-10;
|
|
6571
|
+
|
|
6572
|
+
// check if point is inside triangle
|
|
6573
|
+
const pointInTriangle = (p, a, b, c)=>
|
|
6574
|
+
{
|
|
6575
|
+
const c1 = cross(a, b, p);
|
|
6576
|
+
const c2 = cross(b, c, p);
|
|
6577
|
+
const c3 = cross(c, a, p);
|
|
6578
|
+
const negative = (c1<-e?1:0) + (c2<-e?1:0) + (c3<-e?1:0);
|
|
6579
|
+
const positive = (c1> e?1:0) + (c2> e?1:0) + (c3> e?1:0);
|
|
6580
|
+
return !(negative && positive);
|
|
6581
|
+
};
|
|
6582
|
+
|
|
6583
|
+
// ear clipping triangulation
|
|
6584
|
+
const indices = [];
|
|
6585
|
+
for (let i = 0; i < points.length; ++i)
|
|
6586
|
+
indices[i] = i;
|
|
6587
|
+
const triangles = [];
|
|
6588
|
+
let attempts = 0;
|
|
6589
|
+
const maxAttempts = points.length ** 2 + 100;
|
|
6590
|
+
while (indices.length > 3 && attempts++ < maxAttempts)
|
|
6591
|
+
{
|
|
6592
|
+
let foundEar = false;
|
|
6593
|
+
for (let i = indices.length; --i;)
|
|
6594
|
+
{
|
|
6595
|
+
const i0 = indices[(i + indices.length - 1) % indices.length];
|
|
6596
|
+
const i1 = indices[i];
|
|
6597
|
+
const i2 = indices[(i + 1) % indices.length];
|
|
6598
|
+
const a = points[i0], b = points[i1], c = points[i2];
|
|
6599
|
+
|
|
6600
|
+
// check if convex
|
|
6601
|
+
if (cross(a, b, c) < e)
|
|
6602
|
+
continue;
|
|
6603
|
+
|
|
6604
|
+
// check if any other point is inside
|
|
6605
|
+
let hasInside = false;
|
|
6606
|
+
for (let j = 0; j < indices.length; j++)
|
|
6607
|
+
{
|
|
6608
|
+
const k = indices[j];
|
|
6609
|
+
if (k === i0 || k === i1 || k === i2)
|
|
6610
|
+
continue;
|
|
6611
|
+
const p = points[k];
|
|
6612
|
+
hasInside = pointInTriangle(p, a, b, c);
|
|
6613
|
+
if (hasInside)
|
|
6614
|
+
break;
|
|
6615
|
+
}
|
|
6616
|
+
if (hasInside)
|
|
6617
|
+
continue;
|
|
6618
|
+
|
|
6619
|
+
// found valid ear
|
|
6620
|
+
triangles.push([i0, i1, i2]);
|
|
6621
|
+
indices.splice(i, 1);
|
|
6622
|
+
foundEar = true;
|
|
6623
|
+
break;
|
|
6624
|
+
}
|
|
6625
|
+
|
|
6626
|
+
// fallback for degenerate cases
|
|
6627
|
+
if (!foundEar)
|
|
6628
|
+
{
|
|
6629
|
+
let worstIndex = -1, worstValue = Infinity;
|
|
6630
|
+
for (let i = indices.length; --i;)
|
|
6631
|
+
{
|
|
6632
|
+
const i0 = indices[(i + indices.length - 1) % indices.length];
|
|
6633
|
+
const i1 = indices[i];
|
|
6634
|
+
const i2 = indices[(i + 1) % indices.length];
|
|
6635
|
+
const value = abs(cross(points[i0], points[i1], points[i2]));
|
|
6636
|
+
if (value < worstValue)
|
|
6637
|
+
{
|
|
6638
|
+
worstValue = value;
|
|
6639
|
+
worstIndex = i;
|
|
6640
|
+
}
|
|
6641
|
+
}
|
|
6642
|
+
if (worstIndex < 0)
|
|
6643
|
+
break;
|
|
6644
|
+
|
|
6645
|
+
const i0 = indices[(worstIndex + indices.length - 1) % indices.length];
|
|
6646
|
+
const i1 = indices[worstIndex];
|
|
6647
|
+
const i2 = indices[(worstIndex + 1) % indices.length];
|
|
6648
|
+
triangles.push([i0, i1, i2]);
|
|
6649
|
+
indices.splice(worstIndex, 1);
|
|
6650
|
+
}
|
|
6651
|
+
}
|
|
6652
|
+
|
|
6653
|
+
// add final triangle
|
|
6654
|
+
if (indices.length === 3)
|
|
6655
|
+
triangles.push([indices[0], indices[1], indices[2]]);
|
|
6656
|
+
if (!triangles.length)
|
|
6657
|
+
return [];
|
|
6658
|
+
|
|
6659
|
+
// convert triangles to triangle strip with degenerate connectors
|
|
6660
|
+
const strip = [];
|
|
6661
|
+
let [a0, b0, c0] = triangles[0];
|
|
6662
|
+
strip.push(points[a0], points[b0], points[c0]);
|
|
6663
|
+
for (let i = 1; i < triangles.length; i++)
|
|
6664
|
+
{
|
|
6665
|
+
// add degenerate bridge from last vertex to first of new triangle
|
|
6666
|
+
const [a, b, c] = triangles[i];
|
|
6667
|
+
strip.push(points[c0], points[a]);
|
|
6668
|
+
strip.push(points[a], points[b], points[c]);
|
|
6669
|
+
c0 = c;
|
|
6670
|
+
}
|
|
6671
|
+
return strip;
|
|
6057
6672
|
}
|
|
6058
6673
|
/**
|
|
6059
6674
|
* LittleJS Newgrounds API
|
|
@@ -6078,11 +6693,11 @@ let newgrounds;
|
|
|
6078
6693
|
class NewgroundsMedal extends Medal
|
|
6079
6694
|
{
|
|
6080
6695
|
/** Create a newgrounds medal object and adds it to the list of medals
|
|
6081
|
-
* @param {
|
|
6082
|
-
* @param {
|
|
6083
|
-
* @param {
|
|
6084
|
-
* @param {
|
|
6085
|
-
* @param {
|
|
6696
|
+
* @param {number} id - The unique identifier of the medal
|
|
6697
|
+
* @param {string} name - Name of the medal
|
|
6698
|
+
* @param {string} [description] - Description of the medal
|
|
6699
|
+
* @param {string} [icon] - Icon for the medal
|
|
6700
|
+
* @param {string} [src] - Image location for the medal
|
|
6086
6701
|
*/
|
|
6087
6702
|
constructor(id, name, description, icon, src)
|
|
6088
6703
|
{ super(id, name, description, icon, src); }
|
|
@@ -6174,7 +6789,7 @@ class NewgroundsPlugin
|
|
|
6174
6789
|
* @param {number} id - The scoreboard id
|
|
6175
6790
|
* @param {string} [user] - A user's id or name
|
|
6176
6791
|
* @param {number} [social] - If true, only social scores will be loaded
|
|
6177
|
-
* @param {number} [skip] - Number of scores to skip
|
|
6792
|
+
* @param {number} [skip] - Number of scores to skip over
|
|
6178
6793
|
* @param {number} [limit] - Number of scores to include in the list
|
|
6179
6794
|
* @return {Object} - The response JSON object
|
|
6180
6795
|
*/
|
|
@@ -6397,16 +7012,16 @@ class ZzFXMusic extends Sound
|
|
|
6397
7012
|
if (!soundEnable || headlessMode) return;
|
|
6398
7013
|
this.randomness = 0;
|
|
6399
7014
|
this.sampleChannels = zzfxM(...zzfxMusic);
|
|
6400
|
-
this.sampleRate =
|
|
7015
|
+
this.sampleRate = audioDefaultSampleRate;
|
|
6401
7016
|
}
|
|
6402
7017
|
|
|
6403
|
-
/** Play the music
|
|
6404
|
-
* @param {number} [volume
|
|
6405
|
-
* @param {boolean} [loop] -
|
|
7018
|
+
/** Play the music that loops by default
|
|
7019
|
+
* @param {number} [volume] - Volume to play the music at
|
|
7020
|
+
* @param {boolean} [loop] - Should the music loop?
|
|
6406
7021
|
* @return {AudioBufferSourceNode} - The audio source node
|
|
6407
7022
|
*/
|
|
6408
|
-
playMusic(volume, loop=
|
|
6409
|
-
{ return super.play(undefined, volume, 1,
|
|
7023
|
+
playMusic(volume=1, loop=true)
|
|
7024
|
+
{ return super.play(undefined, volume, 1, 0, loop); }
|
|
6410
7025
|
}
|
|
6411
7026
|
|
|
6412
7027
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -6440,7 +7055,7 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
6440
7055
|
let panning = 0;
|
|
6441
7056
|
let hasMore = 1;
|
|
6442
7057
|
let sampleCache = {};
|
|
6443
|
-
let beatLength =
|
|
7058
|
+
let beatLength = audioDefaultSampleRate / BPM * 60 >> 2;
|
|
6444
7059
|
|
|
6445
7060
|
// for each channel in order until there are no more
|
|
6446
7061
|
for (; hasMore; channelIndex++) {
|
|
@@ -6459,15 +7074,15 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
6459
7074
|
// get next offset, use the length of first channel
|
|
6460
7075
|
nextSampleOffset = outSampleOffset + (patterns[patternIndex][0].length - 2 - (notFirstBeat?0:1)) * beatLength;
|
|
6461
7076
|
// for each beat in pattern, plus one extra if end of sequence
|
|
6462
|
-
isSequenceEnd = sequenceIndex
|
|
7077
|
+
isSequenceEnd = sequenceIndex === sequence.length - 1;
|
|
6463
7078
|
for (i = 2, k = outSampleOffset; i < patternChannel.length + isSequenceEnd; notFirstBeat = ++i) {
|
|
6464
7079
|
|
|
6465
7080
|
// <channel-note>
|
|
6466
7081
|
note = patternChannel[i];
|
|
6467
7082
|
|
|
6468
7083
|
// stop if end, different instrument or new note
|
|
6469
|
-
stop = i
|
|
6470
|
-
instrument
|
|
7084
|
+
stop = i === patternChannel.length + isSequenceEnd - 1 && isSequenceEnd ||
|
|
7085
|
+
instrument !== (patternChannel[0] || 0) || note | 0;
|
|
6471
7086
|
|
|
6472
7087
|
// fill buffer with samples for previous beat, most cpu intensive part
|
|
6473
7088
|
for (j = 0; j < beatLength && notFirstBeat;
|
|
@@ -6552,11 +7167,11 @@ class UISystemPlugin
|
|
|
6552
7167
|
/** @property {Color} - Default text color for UI elements */
|
|
6553
7168
|
this.defaultTextColor = BLACK;
|
|
6554
7169
|
/** @property {Color} - Default button color for UI elements */
|
|
6555
|
-
this.defaultButtonColor = hsl(0,0,.
|
|
7170
|
+
this.defaultButtonColor = hsl(0,0,.7);
|
|
6556
7171
|
/** @property {Color} - Default hover color for UI elements */
|
|
6557
|
-
this.defaultHoverColor = hsl(0,0,.
|
|
7172
|
+
this.defaultHoverColor = hsl(0,0,.9);
|
|
6558
7173
|
/** @property {Color} - Default color for disabled UI elements */
|
|
6559
|
-
this.defaultDisabledColor = hsl(0,0,.
|
|
7174
|
+
this.defaultDisabledColor = hsl(0,0,.3);
|
|
6560
7175
|
/** @property {number} - Default line width for UI elements */
|
|
6561
7176
|
this.defaultLineWidth = 4;
|
|
6562
7177
|
/** @property {number} - Default rounded rect corner radius for UI elements */
|
|
@@ -6576,16 +7191,16 @@ class UISystemPlugin
|
|
|
6576
7191
|
|
|
6577
7192
|
engineAddPlugin(uiUpdate, uiRender);
|
|
6578
7193
|
|
|
6579
|
-
function updateInvisible(o)
|
|
6580
|
-
{
|
|
6581
|
-
for (const c of o.children)
|
|
6582
|
-
updateInvisible(c);
|
|
6583
|
-
o.updateInvisible();
|
|
6584
|
-
}
|
|
6585
|
-
|
|
6586
7194
|
// setup recursive update and render
|
|
6587
7195
|
function uiUpdate()
|
|
6588
7196
|
{
|
|
7197
|
+
function updateInvisibleObject(o)
|
|
7198
|
+
{
|
|
7199
|
+
// update invisible objects
|
|
7200
|
+
for (const c of o.children)
|
|
7201
|
+
updateInvisibleObject(c);
|
|
7202
|
+
o.updateInvisible();
|
|
7203
|
+
}
|
|
6589
7204
|
function updateObject(o)
|
|
6590
7205
|
{
|
|
6591
7206
|
if (o.visible)
|
|
@@ -6599,7 +7214,7 @@ class UISystemPlugin
|
|
|
6599
7214
|
o.update();
|
|
6600
7215
|
}
|
|
6601
7216
|
else
|
|
6602
|
-
|
|
7217
|
+
updateInvisibleObject(o);
|
|
6603
7218
|
}
|
|
6604
7219
|
uiSystem.uiObjects.forEach(o=> o.parent || updateObject(o));
|
|
6605
7220
|
}
|
|
@@ -6625,7 +7240,7 @@ class UISystemPlugin
|
|
|
6625
7240
|
* @param {Color} [color=uiSystem.defaultColor]
|
|
6626
7241
|
* @param {number} [lineWidth=uiSystem.defaultLineWidth]
|
|
6627
7242
|
* @param {Color} [lineColor=uiSystem.defaultLineColor]
|
|
6628
|
-
* @param {number} [
|
|
7243
|
+
* @param {number} [cornerRadius=uiSystem.defaultCornerRadius] */
|
|
6629
7244
|
drawRect(pos, size, color=uiSystem.defaultColor, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor, cornerRadius=uiSystem.defaultCornerRadius)
|
|
6630
7245
|
{
|
|
6631
7246
|
const context = uiSystem.uiContext;
|
|
@@ -6700,32 +7315,40 @@ class UIObject
|
|
|
6700
7315
|
constructor(pos=vec2(), size=vec2())
|
|
6701
7316
|
{
|
|
6702
7317
|
/** @property {Vector2} - Local position of the object */
|
|
6703
|
-
this.localPos
|
|
7318
|
+
this.localPos = pos.copy();
|
|
6704
7319
|
/** @property {Vector2} - Screen space position of the object */
|
|
6705
|
-
this.pos
|
|
7320
|
+
this.pos = pos.copy();
|
|
6706
7321
|
/** @property {Vector2} - Screen space size of the object */
|
|
6707
|
-
this.size
|
|
6708
|
-
/** @property {Color} -
|
|
6709
|
-
this.color
|
|
6710
|
-
/** @property {
|
|
6711
|
-
this.
|
|
6712
|
-
/** @property {Color} -
|
|
7322
|
+
this.size = size.copy();
|
|
7323
|
+
/** @property {Color} - Color of the object */
|
|
7324
|
+
this.color = uiSystem.defaultColor;
|
|
7325
|
+
/** @property {string} - Text for this ui object */
|
|
7326
|
+
this.text = undefined;
|
|
7327
|
+
/** @property {Color} - Color when disabled */
|
|
7328
|
+
this.disabledColor = uiSystem.defaultDisabledColor;
|
|
7329
|
+
/** @property {boolean} - Is this object disabled? */
|
|
7330
|
+
this.disabled = false;
|
|
7331
|
+
/** @property {Color} - Color for text */
|
|
7332
|
+
this.textColor = uiSystem.defaultTextColor;
|
|
7333
|
+
/** @property {Color} - Color used when hovering over the object */
|
|
6713
7334
|
this.hoverColor = uiSystem.defaultHoverColor;
|
|
6714
|
-
/** @property {Color} -
|
|
6715
|
-
this.lineColor
|
|
6716
|
-
/** @property {number} -
|
|
6717
|
-
this.lineWidth
|
|
6718
|
-
/** @property {
|
|
6719
|
-
this.
|
|
6720
|
-
/** @property {
|
|
6721
|
-
this.
|
|
6722
|
-
/** @property {
|
|
6723
|
-
this.
|
|
6724
|
-
/** @property {
|
|
6725
|
-
this.
|
|
6726
|
-
/** @property {UIObject} - this object's
|
|
6727
|
-
this.
|
|
6728
|
-
/** @property {
|
|
7335
|
+
/** @property {Color} - Color for line drawing */
|
|
7336
|
+
this.lineColor = uiSystem.defaultLineColor;
|
|
7337
|
+
/** @property {number} - Width for line drawing */
|
|
7338
|
+
this.lineWidth = uiSystem.defaultLineWidth;
|
|
7339
|
+
/** @property {number} - Corner radius for rounded rects */
|
|
7340
|
+
this.cornerRadius = uiSystem.defaultCornerRadius;
|
|
7341
|
+
/** @property {string} - Font for this objecct */
|
|
7342
|
+
this.font = uiSystem.defaultFont;
|
|
7343
|
+
/** @property {number} - Override for text height */
|
|
7344
|
+
this.textHeight = undefined;
|
|
7345
|
+
/** @property {boolean} - Should this object be drawn */
|
|
7346
|
+
this.visible = true;
|
|
7347
|
+
/** @property {Array<UIObject>} - A list of this object's children */
|
|
7348
|
+
this.children = [];
|
|
7349
|
+
/** @property {UIObject} - This object's parent, position is in parent space */
|
|
7350
|
+
this.parent = undefined;
|
|
7351
|
+
/** @property {number} - Extra size added to make small buttons easier to touch on mobile devices */
|
|
6729
7352
|
this.extraTouchSize = 0;
|
|
6730
7353
|
/** @property {Sound} - Sound when interactive element is pressed */
|
|
6731
7354
|
this.soundPress = uiSystem.defaultSoundPress;
|
|
@@ -6757,7 +7380,7 @@ class UIObject
|
|
|
6757
7380
|
*/
|
|
6758
7381
|
removeChild(child)
|
|
6759
7382
|
{
|
|
6760
|
-
ASSERT(child.parent
|
|
7383
|
+
ASSERT(child.parent === this && this.children.includes(child));
|
|
6761
7384
|
this.children.splice(this.children.indexOf(child), 1);
|
|
6762
7385
|
child.parent = undefined;
|
|
6763
7386
|
}
|
|
@@ -6810,7 +7433,7 @@ class UIObject
|
|
|
6810
7433
|
this.mouseIsHeld = false;
|
|
6811
7434
|
}
|
|
6812
7435
|
|
|
6813
|
-
if (this.mouseIsOver
|
|
7436
|
+
if (this.mouseIsOver !== mouseWasOver)
|
|
6814
7437
|
this.mouseIsOver ? this.onEnter() : this.onLeave();
|
|
6815
7438
|
}
|
|
6816
7439
|
|
|
@@ -6821,7 +7444,7 @@ class UIObject
|
|
|
6821
7444
|
uiSystem.drawRect(this.pos, this.size, this.color, this.lineWidth, this.lineColor, this.cornerRadius);
|
|
6822
7445
|
}
|
|
6823
7446
|
|
|
6824
|
-
/** Special update
|
|
7447
|
+
/** Special update when object is not visible */
|
|
6825
7448
|
updateInvisible()
|
|
6826
7449
|
{
|
|
6827
7450
|
// reset input state when not visible
|
|
@@ -6829,28 +7452,22 @@ class UIObject
|
|
|
6829
7452
|
}
|
|
6830
7453
|
|
|
6831
7454
|
/** Called when the mouse enters the object */
|
|
6832
|
-
onEnter()
|
|
6833
|
-
{}
|
|
7455
|
+
onEnter() {}
|
|
6834
7456
|
|
|
6835
7457
|
/** Called when the mouse leaves the object */
|
|
6836
|
-
onLeave()
|
|
6837
|
-
{}
|
|
7458
|
+
onLeave() {}
|
|
6838
7459
|
|
|
6839
7460
|
/** Called when the mouse is pressed while over the object */
|
|
6840
|
-
onPress()
|
|
6841
|
-
{}
|
|
7461
|
+
onPress() {}
|
|
6842
7462
|
|
|
6843
7463
|
/** Called when the mouse is released while over the object */
|
|
6844
|
-
onRelease()
|
|
6845
|
-
{}
|
|
7464
|
+
onRelease() {}
|
|
6846
7465
|
|
|
6847
7466
|
/** Called when user clicks on this object */
|
|
6848
|
-
onClick()
|
|
6849
|
-
{}
|
|
7467
|
+
onClick() {}
|
|
6850
7468
|
|
|
6851
7469
|
/** Called when the state of this object changes */
|
|
6852
|
-
onChange()
|
|
6853
|
-
{}
|
|
7470
|
+
onChange() {}
|
|
6854
7471
|
};
|
|
6855
7472
|
|
|
6856
7473
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -6871,13 +7488,13 @@ class UIText extends UIObject
|
|
|
6871
7488
|
{
|
|
6872
7489
|
super(pos, size);
|
|
6873
7490
|
|
|
6874
|
-
|
|
7491
|
+
// set properties
|
|
6875
7492
|
this.text = text;
|
|
6876
|
-
/** @property {string} */
|
|
6877
7493
|
this.align = align;
|
|
7494
|
+
this.font = font;
|
|
6878
7495
|
|
|
6879
|
-
|
|
6880
|
-
this.lineWidth = 0;
|
|
7496
|
+
// make text not outlined by default
|
|
7497
|
+
this.lineWidth = 0;
|
|
6881
7498
|
}
|
|
6882
7499
|
render()
|
|
6883
7500
|
{
|
|
@@ -6904,13 +7521,14 @@ class UITile extends UIObject
|
|
|
6904
7521
|
constructor(pos, size, tileInfo, color=WHITE, angle=0, mirror=false)
|
|
6905
7522
|
{
|
|
6906
7523
|
super(pos, size);
|
|
6907
|
-
|
|
6908
7524
|
/** @property {TileInfo} - Tile image to use */
|
|
6909
7525
|
this.tileInfo = tileInfo;
|
|
6910
7526
|
/** @property {number} - Angle to rotate in radians */
|
|
6911
7527
|
this.angle = angle;
|
|
6912
7528
|
/** @property {boolean} - Should it be mirrored? */
|
|
6913
7529
|
this.mirror = mirror;
|
|
7530
|
+
|
|
7531
|
+
// set properties
|
|
6914
7532
|
this.color = color;
|
|
6915
7533
|
}
|
|
6916
7534
|
render()
|
|
@@ -6936,22 +7554,20 @@ class UIButton extends UIObject
|
|
|
6936
7554
|
{
|
|
6937
7555
|
super(pos, size);
|
|
6938
7556
|
|
|
6939
|
-
|
|
7557
|
+
// set properties
|
|
6940
7558
|
this.text = text;
|
|
6941
|
-
/** @property {Color} */
|
|
6942
|
-
this.disabledColor = uiSystem.defaultDisabledColor;
|
|
6943
|
-
/** @property {boolean} */
|
|
6944
|
-
this.disabled = false;
|
|
6945
|
-
this.interactive = true;
|
|
6946
7559
|
this.color = color;
|
|
7560
|
+
this.interactive = true;
|
|
6947
7561
|
}
|
|
6948
7562
|
render()
|
|
6949
7563
|
{
|
|
7564
|
+
// draw the button
|
|
6950
7565
|
const lineColor = this.mouseIsHeld && !this.disabled ? this.color : this.lineColor;
|
|
6951
7566
|
const color = this.disabled ? this.disabledColor : this.mouseIsOver ? this.hoverColor : this.color;
|
|
6952
7567
|
uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius);
|
|
6953
7568
|
|
|
6954
|
-
|
|
7569
|
+
// draw the text
|
|
7570
|
+
const textScale = .8; // scale text to fit
|
|
6955
7571
|
const textSize = vec2(this.size.x, this.textHeight || this.size.y*textScale);
|
|
6956
7572
|
uiSystem.drawText(this.text, this.pos, textSize,
|
|
6957
7573
|
this.textColor, 0, undefined, this.align, this.font);
|
|
@@ -6969,13 +7585,18 @@ class UICheckbox extends UIObject
|
|
|
6969
7585
|
* @param {Vector2} [pos]
|
|
6970
7586
|
* @param {Vector2} [size]
|
|
6971
7587
|
* @param {boolean} [checked]
|
|
7588
|
+
* @param {string} [text]
|
|
7589
|
+
* @param {Color} [color=uiSystem.defaultButtonColor]
|
|
6972
7590
|
*/
|
|
6973
|
-
constructor(pos, size, checked=false)
|
|
7591
|
+
constructor(pos, size, checked=false, text='', color=uiSystem.defaultButtonColor)
|
|
6974
7592
|
{
|
|
6975
7593
|
super(pos, size);
|
|
6976
|
-
|
|
6977
|
-
/** @property {boolean} */
|
|
7594
|
+
/** @property {boolean} - Current percentage value of this scrollbar 0-1 */
|
|
6978
7595
|
this.checked = checked;
|
|
7596
|
+
|
|
7597
|
+
// set properties
|
|
7598
|
+
this.text = text;
|
|
7599
|
+
this.color = color;
|
|
6979
7600
|
this.interactive = true;
|
|
6980
7601
|
}
|
|
6981
7602
|
onClick()
|
|
@@ -6985,14 +7606,24 @@ class UICheckbox extends UIObject
|
|
|
6985
7606
|
}
|
|
6986
7607
|
render()
|
|
6987
7608
|
{
|
|
6988
|
-
const color = this.mouseIsOver? this.hoverColor : this.color;
|
|
7609
|
+
const color = this.disabled ? this.disabledColor : this.mouseIsOver ? this.hoverColor : this.color;
|
|
6989
7610
|
uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, this.lineColor, this.cornerRadius);
|
|
6990
7611
|
if (this.checked)
|
|
6991
7612
|
{
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
7613
|
+
const p = this.cornerRadius / min(this.size.x, this.size.y) * 2;
|
|
7614
|
+
const length = lerp(1, 2**.5/2, p) / 2;
|
|
7615
|
+
let s = this.size.scale(length);
|
|
7616
|
+
uiSystem.drawLine(this.pos.add(s.multiply(vec2(-1))), this.pos.add(s.multiply(vec2(1))), this.lineWidth, this.lineColor);
|
|
7617
|
+
uiSystem.drawLine(this.pos.add(s.multiply(vec2(-1,1))), this.pos.add(s.multiply(vec2(1,-1))), this.lineWidth, this.lineColor);
|
|
6995
7618
|
}
|
|
7619
|
+
|
|
7620
|
+
// draw the text to the right side of the checkbox
|
|
7621
|
+
const textScale = .8; // scale text to fit
|
|
7622
|
+
const gapScale = .55;
|
|
7623
|
+
const textSize = vec2(this.size.x, this.textHeight || this.size.y*textScale);
|
|
7624
|
+
const pos = this.pos.add(vec2(this.size.x*gapScale,0));
|
|
7625
|
+
uiSystem.drawText(this.text, pos, textSize,
|
|
7626
|
+
this.textColor, 0, undefined, 'left', this.font);
|
|
6996
7627
|
}
|
|
6997
7628
|
}
|
|
6998
7629
|
|
|
@@ -7015,43 +7646,51 @@ class UIScrollbar extends UIObject
|
|
|
7015
7646
|
{
|
|
7016
7647
|
super(pos, size);
|
|
7017
7648
|
|
|
7018
|
-
/** @property {number} */
|
|
7649
|
+
/** @property {number} - Current percentage value of this scrollbar 0-1 */
|
|
7019
7650
|
this.value = value;
|
|
7020
|
-
/** @property {
|
|
7021
|
-
this.text = text;
|
|
7022
|
-
/** @property {Color} */
|
|
7651
|
+
/** @property {Color} - Color for the handle part of the scrollbar */
|
|
7023
7652
|
this.handleColor = handleColor;
|
|
7653
|
+
|
|
7654
|
+
// set properties
|
|
7655
|
+
this.text = text;
|
|
7024
7656
|
this.color = color;
|
|
7025
7657
|
this.interactive = true;
|
|
7026
7658
|
}
|
|
7027
7659
|
update()
|
|
7028
7660
|
{
|
|
7029
7661
|
super.update();
|
|
7030
|
-
if (this.mouseIsHeld)
|
|
7662
|
+
if (this.mouseIsHeld && this.interactive)
|
|
7031
7663
|
{
|
|
7664
|
+
// check if value changed
|
|
7032
7665
|
const handleSize = vec2(this.size.y);
|
|
7033
7666
|
const handleWidth = this.size.x - handleSize.x;
|
|
7034
7667
|
const p1 = this.pos.x - handleWidth/2;
|
|
7035
7668
|
const p2 = this.pos.x + handleWidth/2;
|
|
7036
7669
|
const oldValue = this.value;
|
|
7037
7670
|
this.value = percent(mousePosScreen.x, p1, p2);
|
|
7038
|
-
this.value
|
|
7671
|
+
this.value === oldValue || this.onChange();
|
|
7039
7672
|
}
|
|
7040
7673
|
}
|
|
7041
7674
|
render()
|
|
7042
7675
|
{
|
|
7043
|
-
|
|
7044
|
-
const
|
|
7676
|
+
// draw the scrollbar background
|
|
7677
|
+
const lineColor = this.interactive && this.mouseIsHeld && !this.disabled ?
|
|
7678
|
+
this.color : this.lineColor;
|
|
7679
|
+
const color = this.disabled ? this.disabledColor :
|
|
7680
|
+
this.interactive && this.mouseIsHeld ? this.hoverColor : this.color;
|
|
7045
7681
|
uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius);
|
|
7046
7682
|
|
|
7683
|
+
// draw the scrollbar handle
|
|
7047
7684
|
const handleSize = vec2(this.size.y);
|
|
7048
7685
|
const handleWidth = this.size.x - handleSize.x;
|
|
7049
7686
|
const p1 = this.pos.x - handleWidth/2;
|
|
7050
7687
|
const p2 = this.pos.x + handleWidth/2;
|
|
7051
7688
|
const handlePos = vec2(lerp(p1, p2, this.value), this.pos.y);
|
|
7052
|
-
const
|
|
7053
|
-
|
|
7689
|
+
const handleColor = this.disabled ? this.disabledColor :
|
|
7690
|
+
this.interactive && this.mouseIsHeld ? this.color : this.handleColor;
|
|
7691
|
+
uiSystem.drawRect(handlePos, handleSize, handleColor, this.lineWidth, this.lineColor, this.cornerRadius);
|
|
7054
7692
|
|
|
7693
|
+
// draw the text on the scrollbar
|
|
7055
7694
|
const textScale = .8; // scale text to fit in scrollbar
|
|
7056
7695
|
const textSize = vec2(this.size.x, this.textHeight || this.size.y*textScale);
|
|
7057
7696
|
uiSystem.drawText(this.text, this.pos, textSize,
|
|
@@ -7146,14 +7785,14 @@ class Box2dObject extends EngineObject
|
|
|
7146
7785
|
if (this.tileInfo)
|
|
7147
7786
|
super.render();
|
|
7148
7787
|
else
|
|
7149
|
-
this.drawFixtures(this.color, this.lineColor, this.lineWidth
|
|
7788
|
+
this.drawFixtures(this.color, this.lineColor, this.lineWidth);
|
|
7150
7789
|
}
|
|
7151
7790
|
|
|
7152
7791
|
/** Render debug info */
|
|
7153
7792
|
renderDebugInfo()
|
|
7154
7793
|
{
|
|
7155
7794
|
const isAsleep = !this.getIsAwake();
|
|
7156
|
-
const isStatic = this.getBodyType()
|
|
7795
|
+
const isStatic = this.getBodyType() === box2d.bodyTypeStatic;
|
|
7157
7796
|
const color = rgb(isAsleep?1:0, isAsleep?1:0, isStatic?1:0, .5);
|
|
7158
7797
|
this.drawFixtures(color);
|
|
7159
7798
|
}
|
|
@@ -8621,7 +9260,7 @@ class Box2dPlugin
|
|
|
8621
9260
|
queryCallback.ReportFixture = function(fixturePointer)
|
|
8622
9261
|
{
|
|
8623
9262
|
const fixture = box2d.instance.wrapPointer(fixturePointer, box2d.instance.b2Fixture);
|
|
8624
|
-
if (dynamicOnly && fixture.GetBody().GetType()
|
|
9263
|
+
if (dynamicOnly && fixture.GetBody().GetType() !== box2d.instance.b2_dynamicBody)
|
|
8625
9264
|
return true; // continue getting results
|
|
8626
9265
|
if (!fixture.TestPoint(box2d.vec2dTo(pos)))
|
|
8627
9266
|
return true; // continue getting results
|
|
@@ -8650,7 +9289,7 @@ class Box2dPlugin
|
|
|
8650
9289
|
* @param {Color} [lineColor]
|
|
8651
9290
|
* @param {number} [lineWidth]
|
|
8652
9291
|
* @param {CanvasRenderingContext2D} [context] */
|
|
8653
|
-
drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1, context
|
|
9292
|
+
drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1, context)
|
|
8654
9293
|
{
|
|
8655
9294
|
const shape = box2d.castObjectType(fixture.GetShape());
|
|
8656
9295
|
switch (shape.GetType())
|
|
@@ -8660,20 +9299,20 @@ class Box2dPlugin
|
|
|
8660
9299
|
let points = [];
|
|
8661
9300
|
for (let i=shape.GetVertexCount(); i--;)
|
|
8662
9301
|
points.push(box2d.vec2From(shape.GetVertex(i)));
|
|
8663
|
-
drawPoly(points, color, lineWidth, lineColor, pos, angle
|
|
9302
|
+
drawPoly(points, color, lineWidth, lineColor, pos, angle);
|
|
8664
9303
|
break;
|
|
8665
9304
|
}
|
|
8666
9305
|
case box2d.instance.b2Shape.e_circle:
|
|
8667
9306
|
{
|
|
8668
9307
|
const radius = shape.get_m_radius();
|
|
8669
|
-
drawCircle(pos, radius, color, lineWidth, lineColor
|
|
9308
|
+
drawCircle(pos, radius, color, lineWidth, lineColor);
|
|
8670
9309
|
break;
|
|
8671
9310
|
}
|
|
8672
9311
|
case box2d.instance.b2Shape.e_edge:
|
|
8673
9312
|
{
|
|
8674
9313
|
const v1 = box2d.vec2From(shape.get_m_vertex1());
|
|
8675
9314
|
const v2 = box2d.vec2From(shape.get_m_vertex2());
|
|
8676
|
-
drawLine(v1, v2, lineWidth, lineColor, pos, angle
|
|
9315
|
+
drawLine(v1, v2, lineWidth, lineColor, pos, angle);
|
|
8677
9316
|
break;
|
|
8678
9317
|
}
|
|
8679
9318
|
}
|
|
@@ -8752,7 +9391,9 @@ class Box2dPlugin
|
|
|
8752
9391
|
}
|
|
8753
9392
|
|
|
8754
9393
|
///////////////////////////////////////////////////////////////////////////////
|
|
8755
|
-
/** Box2d Init - Call with await
|
|
9394
|
+
/** Box2d Init - Call with await to init box2d
|
|
9395
|
+
* @example
|
|
9396
|
+
* await box2dInit();
|
|
8756
9397
|
* @return {Promise<Box2dPlugin>}
|
|
8757
9398
|
* @memberof Box2D */
|
|
8758
9399
|
async function box2dInit()
|
|
@@ -8868,7 +9509,7 @@ function drawNineSliceScreen(pos, size, startTile, borderSize=32, extraSpace=2,
|
|
|
8868
9509
|
}
|
|
8869
9510
|
|
|
8870
9511
|
/** Draw a scalable nine-slice UI element in world space
|
|
8871
|
-
* This function can apply color and additive color if
|
|
9512
|
+
* This function can apply color and additive color if WebGL is enabled
|
|
8872
9513
|
* @param {Vector2} pos - World space position
|
|
8873
9514
|
* @param {Vector2} size - World space size
|
|
8874
9515
|
* @param {TileInfo} startTile - Starting tile for the nine-slice pattern
|
|
@@ -8897,9 +9538,9 @@ function drawNineSlice(pos, size, startTile, color, borderSize=1, additiveColor,
|
|
|
8897
9538
|
{
|
|
8898
9539
|
// sides
|
|
8899
9540
|
const horizontal = i%2;
|
|
8900
|
-
const sidePos = cornerOffset.multiply(vec2(horizontal?i
|
|
9541
|
+
const sidePos = cornerOffset.multiply(vec2(horizontal?i===1?1:-1:0, horizontal?0:i?-1:1));
|
|
8901
9542
|
const sideSize = vec2(horizontal ? borderSize : centerSize.x, horizontal ? centerSize.y : borderSize);
|
|
8902
|
-
const sideTile = centerTile.offset(startTile.size.multiply(vec2(i
|
|
9543
|
+
const sideTile = centerTile.offset(startTile.size.multiply(vec2(i===1?1:i===3?-1:0,i===0?-flip:i===2?flip:0)))
|
|
8903
9544
|
drawTile(pos.add(sidePos.rotate(rotateAngle)), sideSize, sideTile, color, angle, false, additiveColor, useWebGL, screenSpace, context);
|
|
8904
9545
|
}
|
|
8905
9546
|
for (let i=4; i--;)
|
|
@@ -8928,7 +9569,7 @@ function drawThreeSliceScreen(pos, size, startTile, borderSize=32, extraSpace=2,
|
|
|
8928
9569
|
}
|
|
8929
9570
|
|
|
8930
9571
|
/** Draw a scalable three-slice UI element in world space
|
|
8931
|
-
* This function can apply color and additive color if
|
|
9572
|
+
* This function can apply color and additive color if WebGL is enabled
|
|
8932
9573
|
* @param {Vector2} pos - World space position
|
|
8933
9574
|
* @param {Vector2} size - World space size
|
|
8934
9575
|
* @param {TileInfo} startTile - Starting tile for the three-slice pattern
|
|
@@ -8960,7 +9601,7 @@ function drawThreeSlice(pos, size, startTile, color, borderSize=1, additiveColor
|
|
|
8960
9601
|
// sides
|
|
8961
9602
|
const a = angle + i*PI/2;
|
|
8962
9603
|
const horizontal = i%2;
|
|
8963
|
-
const sidePos = cornerOffset.multiply(vec2(horizontal?i
|
|
9604
|
+
const sidePos = cornerOffset.multiply(vec2(horizontal?i===1?1:-1:0, horizontal?0:i?-flip:flip));
|
|
8964
9605
|
const sideSize = vec2(horizontal ? centerSize.y : centerSize.x, borderSize);
|
|
8965
9606
|
drawTile(pos.add(sidePos.rotate(rotateAngle)), sideSize, sideTile, color, a, false, additiveColor, useWebGL, screenSpace, context);
|
|
8966
9607
|
}
|