littlejsengine 1.13.4 → 1.14.4
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 +4 -3
- package/dist/littlejs.d.ts +395 -249
- package/dist/littlejs.esm.js +1550 -754
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +1528 -744
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +1504 -720
- package/examples/box2d/game.js +4 -4
- package/examples/box2d/gameObjects.js +1 -1
- package/examples/breakout/game.js +30 -25
- package/examples/breakoutTutorial/README.md +1 -1
- package/examples/breakoutTutorial/game.js +1 -1
- package/examples/electron/build.js +1 -1
- package/examples/electron/index.html +2 -2
- package/examples/empty/game.js +1 -1
- package/examples/htmlMenu/index.html +7 -7
- package/examples/index.html +208 -97
- package/examples/platformer/gameEffects.js +20 -25
- 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 +12 -18
- 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 +14 -7
- package/examples/style.css +14 -4
- package/examples/typescript/build.js +1 -1
- package/examples/uiSystem/game.js +11 -11
- package/package.json +1 -1
- package/plugins/box2d.js +12 -10
- 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 +94 -56
- package/src/engine.js +28 -24
- package/src/engineAudio.js +284 -109
- package/src/engineBuild.js +11 -11
- package/src/engineDebug.js +29 -29
- package/src/engineDraw.js +285 -112
- package/src/engineExport.js +28 -16
- package/src/engineInput.js +57 -67
- package/src/engineMedals.js +25 -25
- package/src/engineObject.js +28 -25
- package/src/engineParticles.js +59 -59
- package/src/engineRelease.js +1 -1
- package/src/engineSettings.js +20 -13
- package/src/engineTileLayer.js +34 -35
- package/src/engineUtilities.js +66 -59
- package/src/engineWebGL.js +466 -66
- /package/examples/shorts/{playSound.js → sound.js} +0 -0
package/dist/littlejs.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.4';
|
|
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,15 +242,14 @@ 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);
|
|
249
249
|
overlayContext.fillStyle = '#fff';
|
|
250
250
|
overlayContext.fillText(text, mainCanvas.width-2, 2);
|
|
251
251
|
}
|
|
252
|
-
|
|
253
|
-
drawCount = 0;
|
|
252
|
+
drawCount = 0;
|
|
254
253
|
}
|
|
255
254
|
requestAnimationFrame(engineUpdate);
|
|
256
255
|
}
|
|
@@ -258,13 +257,13 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
258
257
|
function updateCanvas()
|
|
259
258
|
{
|
|
260
259
|
if (headlessMode) return;
|
|
261
|
-
|
|
260
|
+
|
|
262
261
|
if (canvasFixedSize.x)
|
|
263
262
|
{
|
|
264
263
|
// clear canvas and set fixed size
|
|
265
264
|
mainCanvas.width = canvasFixedSize.x;
|
|
266
265
|
mainCanvas.height = canvasFixedSize.y;
|
|
267
|
-
|
|
266
|
+
|
|
268
267
|
// fit to window by adding space on top or bottom if necessary
|
|
269
268
|
const aspect = innerWidth / innerHeight;
|
|
270
269
|
const fixedAspect = mainCanvas.width / mainCanvas.height;
|
|
@@ -274,16 +273,21 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
274
273
|
else
|
|
275
274
|
{
|
|
276
275
|
// clear canvas and set size to same as window
|
|
277
|
-
mainCanvas.width
|
|
276
|
+
mainCanvas.width = min(innerWidth, canvasMaxSize.x);
|
|
278
277
|
mainCanvas.height = min(innerHeight, canvasMaxSize.y);
|
|
279
278
|
}
|
|
280
|
-
|
|
279
|
+
|
|
281
280
|
// clear overlay canvas and set size
|
|
282
281
|
overlayCanvas.width = mainCanvas.width;
|
|
283
282
|
overlayCanvas.height = mainCanvas.height;
|
|
284
283
|
|
|
285
284
|
// save canvas size
|
|
286
285
|
mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
|
|
286
|
+
|
|
287
|
+
// set default line join and cap
|
|
288
|
+
const lineJoin = 'round', lineCap = 'round';
|
|
289
|
+
mainContext.lineJoin = overlayContext.lineJoin = lineJoin;
|
|
290
|
+
mainContext.lineCap = overlayContext.lineCap = lineCap;
|
|
287
291
|
}
|
|
288
292
|
|
|
289
293
|
// wait for gameInit to load
|
|
@@ -296,15 +300,14 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
296
300
|
return startEngine();
|
|
297
301
|
|
|
298
302
|
// setup html
|
|
299
|
-
const styleRoot =
|
|
303
|
+
const styleRoot =
|
|
300
304
|
'margin:0;' + // fill the window
|
|
301
305
|
'overflow:hidden;' + // no scroll bars
|
|
302
306
|
'background:#000;' + // set background color
|
|
303
307
|
'user-select:none;' + // prevent hold to select
|
|
304
308
|
'-webkit-user-select:none;' + // compatibility for ios
|
|
305
|
-
(!touchInputEnable ? '' : // no touch css settings
|
|
306
309
|
'touch-action:none;' + // prevent mobile pinch to resize
|
|
307
|
-
'-webkit-touch-callout:none'
|
|
310
|
+
'-webkit-touch-callout:none';// compatibility for ios
|
|
308
311
|
rootElement.style.cssText = styleRoot;
|
|
309
312
|
drawCanvas = mainCanvas = document.createElement('canvas');
|
|
310
313
|
rootElement.appendChild(mainCanvas);
|
|
@@ -321,7 +324,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
321
324
|
rootElement.appendChild(overlayCanvas);
|
|
322
325
|
overlayContext = overlayCanvas.getContext('2d');
|
|
323
326
|
|
|
324
|
-
// set
|
|
327
|
+
// set canvases
|
|
325
328
|
const styleCanvas = 'position:absolute;'+ // allow canvases to overlap
|
|
326
329
|
'top:50%;left:50%;transform:translate(-50%,-50%)'; // center on screen
|
|
327
330
|
mainCanvas.style.cssText = overlayCanvas.style.cssText = styleCanvas;
|
|
@@ -330,17 +333,18 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
330
333
|
setCanvasPixelated(canvasPixelated);
|
|
331
334
|
setOverlayCanvasPixelated(overlayCanvasPixelated);
|
|
332
335
|
updateCanvas();
|
|
336
|
+
glPreRender();
|
|
333
337
|
|
|
334
338
|
// create offscreen canvas for image processing
|
|
335
339
|
workCanvas = new OffscreenCanvas(256, 256);
|
|
336
340
|
workContext = workCanvas.getContext('2d', { willReadFrequently: true });
|
|
337
|
-
|
|
341
|
+
|
|
338
342
|
// create promises for loading images
|
|
339
343
|
const promises = imageSources.map((src, textureIndex)=>
|
|
340
|
-
new Promise(resolve =>
|
|
344
|
+
new Promise(resolve =>
|
|
341
345
|
{
|
|
342
346
|
const image = new Image;
|
|
343
|
-
image.onerror = image.onload = ()=>
|
|
347
|
+
image.onerror = image.onload = ()=>
|
|
344
348
|
{
|
|
345
349
|
const textureInfo = new TextureInfo(image);
|
|
346
350
|
textureInfo.createWebGLTexture();
|
|
@@ -355,7 +359,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
355
359
|
if (!imageSources.length)
|
|
356
360
|
{
|
|
357
361
|
// no images to load
|
|
358
|
-
promises.push(new Promise(resolve =>
|
|
362
|
+
promises.push(new Promise(resolve =>
|
|
359
363
|
{
|
|
360
364
|
const textureInfo = new TextureInfo(new Image);
|
|
361
365
|
textureInfos[0] = textureInfo;
|
|
@@ -367,7 +371,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
367
371
|
if (showSplashScreen)
|
|
368
372
|
{
|
|
369
373
|
// draw splash screen
|
|
370
|
-
promises.push(new Promise(resolve =>
|
|
374
|
+
promises.push(new Promise(resolve =>
|
|
371
375
|
{
|
|
372
376
|
let t = 0;
|
|
373
377
|
console.log(`${engineName} Engine v${engineVersion}`);
|
|
@@ -599,7 +603,7 @@ function drawEngineSplashScreen(t)
|
|
|
599
603
|
line(36,20,60,20);
|
|
600
604
|
|
|
601
605
|
// engine front light
|
|
602
|
-
circle(60,30,4,PI,3*PI,color(3,2));
|
|
606
|
+
circle(60,30,4,PI,3*PI,color(3,2));
|
|
603
607
|
circle(60,30,4,PI,2*PI,color(3,3));
|
|
604
608
|
circle(60,30,4,PI,3*PI);
|
|
605
609
|
|
|
@@ -648,10 +652,10 @@ function drawEngineSplashScreen(t)
|
|
|
648
652
|
x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
|
|
649
653
|
X += w;
|
|
650
654
|
}
|
|
651
|
-
|
|
655
|
+
|
|
652
656
|
x.restore();
|
|
653
657
|
}
|
|
654
|
-
/**
|
|
658
|
+
/**
|
|
655
659
|
* LittleJS Debug System
|
|
656
660
|
* - Press Esc to show debug overlay with mouse pick
|
|
657
661
|
* - Number keys toggle debug functions
|
|
@@ -704,10 +708,10 @@ let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParti
|
|
|
704
708
|
// Debug helper functions
|
|
705
709
|
|
|
706
710
|
/** Asserts if the expression is false, does not do anything in release builds
|
|
707
|
-
* @param {boolean} assert
|
|
711
|
+
* @param {boolean} assert
|
|
708
712
|
* @param {...Object} [output] - error message output
|
|
709
713
|
* @memberof Debug */
|
|
710
|
-
function ASSERT(assert, ...output)
|
|
714
|
+
function ASSERT(assert, ...output)
|
|
711
715
|
{
|
|
712
716
|
if (enableAsserts)
|
|
713
717
|
console.assert(assert, ...output);
|
|
@@ -723,9 +727,9 @@ function ASSERT(assert, ...output)
|
|
|
723
727
|
* @memberof Debug */
|
|
724
728
|
function debugRect(pos, size=vec2(), color='#fff', time=0, angle=0, fill=false)
|
|
725
729
|
{
|
|
726
|
-
if (typeof size
|
|
730
|
+
if (typeof size === 'number')
|
|
727
731
|
size = vec2(size); // allow passing in floats
|
|
728
|
-
ASSERT(typeof color
|
|
732
|
+
ASSERT(typeof color === 'string', 'pass in css color strings');
|
|
729
733
|
debugPrimitives.push({pos, size, color, time:new Timer(time), angle, fill});
|
|
730
734
|
}
|
|
731
735
|
|
|
@@ -739,7 +743,7 @@ function debugRect(pos, size=vec2(), color='#fff', time=0, angle=0, fill=false)
|
|
|
739
743
|
* @memberof Debug */
|
|
740
744
|
function debugPoly(pos, points, color='#fff', time=0, angle=0, fill=false)
|
|
741
745
|
{
|
|
742
|
-
ASSERT(typeof color
|
|
746
|
+
ASSERT(typeof color === 'string', 'pass in css color strings');
|
|
743
747
|
debugPrimitives.push({pos, points, color, time:new Timer(time), angle, fill});
|
|
744
748
|
}
|
|
745
749
|
|
|
@@ -752,7 +756,7 @@ function debugPoly(pos, points, color='#fff', time=0, angle=0, fill=false)
|
|
|
752
756
|
* @memberof Debug */
|
|
753
757
|
function debugCircle(pos, radius=0, color='#fff', time=0, fill=false)
|
|
754
758
|
{
|
|
755
|
-
ASSERT(typeof color
|
|
759
|
+
ASSERT(typeof color === 'string', 'pass in css color strings');
|
|
756
760
|
debugPrimitives.push({pos, size:radius, color, time:new Timer(time), angle:0, fill});
|
|
757
761
|
}
|
|
758
762
|
|
|
@@ -764,7 +768,7 @@ function debugCircle(pos, radius=0, color='#fff', time=0, fill=false)
|
|
|
764
768
|
* @memberof Debug */
|
|
765
769
|
function debugPoint(pos, color, time, angle)
|
|
766
770
|
{
|
|
767
|
-
ASSERT(typeof color
|
|
771
|
+
ASSERT(typeof color === 'string', 'pass in css color strings');
|
|
768
772
|
debugRect(pos, undefined, color, time, angle);
|
|
769
773
|
}
|
|
770
774
|
|
|
@@ -772,13 +776,13 @@ function debugPoint(pos, color, time, angle)
|
|
|
772
776
|
* @param {Vector2} posA
|
|
773
777
|
* @param {Vector2} posB
|
|
774
778
|
* @param {string} [color]
|
|
775
|
-
* @param {number} [
|
|
779
|
+
* @param {number} [width]
|
|
776
780
|
* @param {number} [time]
|
|
777
781
|
* @memberof Debug */
|
|
778
|
-
function debugLine(posA, posB, color,
|
|
782
|
+
function debugLine(posA, posB, color, width=.1, time)
|
|
779
783
|
{
|
|
780
784
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
781
|
-
const size = vec2(
|
|
785
|
+
const size = vec2(width, halfDelta.length()*2);
|
|
782
786
|
debugRect(posA.add(halfDelta), size, color, time, halfDelta.angle(), true);
|
|
783
787
|
}
|
|
784
788
|
|
|
@@ -792,11 +796,11 @@ function debugLine(posA, posB, color, thickness=.1, time)
|
|
|
792
796
|
function debugOverlap(posA, sizeA, posB, sizeB, color)
|
|
793
797
|
{
|
|
794
798
|
const minPos = vec2(
|
|
795
|
-
min(posA.x - sizeA.x/2, posB.x - sizeB.x/2),
|
|
799
|
+
min(posA.x - sizeA.x/2, posB.x - sizeB.x/2),
|
|
796
800
|
min(posA.y - sizeA.y/2, posB.y - sizeB.y/2)
|
|
797
801
|
);
|
|
798
802
|
const maxPos = vec2(
|
|
799
|
-
max(posA.x + sizeA.x/2, posB.x + sizeB.x/2),
|
|
803
|
+
max(posA.x + sizeA.x/2, posB.x + sizeB.x/2),
|
|
800
804
|
max(posA.y + sizeA.y/2, posB.y + sizeB.y/2)
|
|
801
805
|
);
|
|
802
806
|
debugRect(minPos.lerp(maxPos,.5), maxPos.subtract(minPos), color);
|
|
@@ -813,7 +817,7 @@ function debugOverlap(posA, sizeA, posB, sizeB, color)
|
|
|
813
817
|
* @memberof Debug */
|
|
814
818
|
function debugText(text, pos, size=1, color='#fff', time=0, angle=0, font='monospace')
|
|
815
819
|
{
|
|
816
|
-
ASSERT(typeof color
|
|
820
|
+
ASSERT(typeof color === 'string', 'pass in css color strings');
|
|
817
821
|
debugPrimitives.push({text, pos, size, color, time:new Timer(time), angle, font});
|
|
818
822
|
}
|
|
819
823
|
|
|
@@ -825,7 +829,7 @@ function debugClear() { debugPrimitives = []; }
|
|
|
825
829
|
* @memberof Debug */
|
|
826
830
|
function debugScreenshot() { debugTakeScreenshot = 1; }
|
|
827
831
|
|
|
828
|
-
/** Save a canvas to disk
|
|
832
|
+
/** Save a canvas to disk
|
|
829
833
|
* @param {HTMLCanvasElement|OffscreenCanvas} canvas
|
|
830
834
|
* @param {string} [filename]
|
|
831
835
|
* @param {string} [type]
|
|
@@ -846,7 +850,7 @@ function debugSaveCanvas(canvas, filename='screenshot', type='image/png')
|
|
|
846
850
|
debugSaveDataURL(canvas.toDataURL(type), filename);
|
|
847
851
|
}
|
|
848
852
|
|
|
849
|
-
/** Save a text file to disk
|
|
853
|
+
/** Save a text file to disk
|
|
850
854
|
* @param {string} text
|
|
851
855
|
* @param {string} [filename]
|
|
852
856
|
* @param {string} [type]
|
|
@@ -854,7 +858,7 @@ function debugSaveCanvas(canvas, filename='screenshot', type='image/png')
|
|
|
854
858
|
function debugSaveText(text, filename='text', type='text/plain')
|
|
855
859
|
{ debugSaveDataURL(URL.createObjectURL(new Blob([text], {'type':type})), filename); }
|
|
856
860
|
|
|
857
|
-
/** Save a data url to disk
|
|
861
|
+
/** Save a data url to disk
|
|
858
862
|
* @param {string} dataURL
|
|
859
863
|
* @param {string} filename
|
|
860
864
|
* @memberof Debug */
|
|
@@ -973,7 +977,7 @@ function debugRender()
|
|
|
973
977
|
{
|
|
974
978
|
const saveContext = mainContext;
|
|
975
979
|
mainContext = overlayContext;
|
|
976
|
-
|
|
980
|
+
|
|
977
981
|
// draw red rectangle around screen
|
|
978
982
|
const cameraSize = getCameraSize();
|
|
979
983
|
debugRect(cameraPos, cameraSize.subtract(vec2(.1)), '#f008');
|
|
@@ -1023,14 +1027,14 @@ function debugRender()
|
|
|
1023
1027
|
overlayContext.scale(1, p.text ? 1 : -1);
|
|
1024
1028
|
overlayContext.fillStyle = overlayContext.strokeStyle = p.color;
|
|
1025
1029
|
|
|
1026
|
-
if (p.text
|
|
1030
|
+
if (p.text !== undefined)
|
|
1027
1031
|
{
|
|
1028
1032
|
overlayContext.font = p.size*cameraScale + 'px '+ p.font;
|
|
1029
1033
|
overlayContext.textAlign = 'center';
|
|
1030
1034
|
overlayContext.textBaseline = 'middle';
|
|
1031
1035
|
overlayContext.fillText(p.text, 0, 0);
|
|
1032
1036
|
}
|
|
1033
|
-
else if (p.points
|
|
1037
|
+
else if (p.points !== undefined)
|
|
1034
1038
|
{
|
|
1035
1039
|
// poly
|
|
1036
1040
|
overlayContext.beginPath();
|
|
@@ -1043,13 +1047,13 @@ function debugRender()
|
|
|
1043
1047
|
p.fill && overlayContext.fill();
|
|
1044
1048
|
overlayContext.stroke();
|
|
1045
1049
|
}
|
|
1046
|
-
else if (p.size
|
|
1050
|
+
else if (p.size === 0 || p.size.x === 0 && p.size.y === 0)
|
|
1047
1051
|
{
|
|
1048
1052
|
// point
|
|
1049
1053
|
overlayContext.fillRect(-pointSize/2, -1, pointSize, 3);
|
|
1050
1054
|
overlayContext.fillRect(-1, -pointSize/2, 3, pointSize);
|
|
1051
1055
|
}
|
|
1052
|
-
else if (p.size.x
|
|
1056
|
+
else if (p.size.x !== undefined)
|
|
1053
1057
|
{
|
|
1054
1058
|
// rect
|
|
1055
1059
|
const s = p.size.scale(cameraScale).floor();
|
|
@@ -1065,14 +1069,14 @@ function debugRender()
|
|
|
1065
1069
|
p.fill && overlayContext.fill();
|
|
1066
1070
|
overlayContext.stroke();
|
|
1067
1071
|
}
|
|
1068
|
-
|
|
1072
|
+
|
|
1069
1073
|
overlayContext.restore();
|
|
1070
1074
|
});
|
|
1071
1075
|
|
|
1072
1076
|
// remove expired primitives
|
|
1073
1077
|
debugPrimitives = debugPrimitives.filter(r=>r.time<0);
|
|
1074
1078
|
}
|
|
1075
|
-
|
|
1079
|
+
|
|
1076
1080
|
if (debugObject)
|
|
1077
1081
|
{
|
|
1078
1082
|
const saveContext = mainContext;
|
|
@@ -1081,8 +1085,8 @@ function debugRender()
|
|
|
1081
1085
|
raycastHitPos && drawRect(raycastHitPos.floor().add(vec2(.5)), vec2(1), rgb(0,1,1,.3));
|
|
1082
1086
|
drawLine(mousePos, debugObject.pos, .1, raycastHitPos ? rgb(1,0,0,.5) : rgb(0,1,0,.5));
|
|
1083
1087
|
|
|
1084
|
-
const debugText = 'mouse pos = ' + mousePos +
|
|
1085
|
-
'\nmouse collision = ' + tileCollisionGetData(mousePos) +
|
|
1088
|
+
const debugText = 'mouse pos = ' + mousePos +
|
|
1089
|
+
'\nmouse collision = ' + tileCollisionGetData(mousePos) +
|
|
1086
1090
|
'\n\n--- object info ---\n' +
|
|
1087
1091
|
debugObject.toString();
|
|
1088
1092
|
drawTextScreen(debugText, mousePosScreen, 24, rgb(), .05, undefined, 'center', 'monospace');
|
|
@@ -1104,7 +1108,7 @@ function debugRender()
|
|
|
1104
1108
|
let x = 9, y = 0, h = lineHeight;
|
|
1105
1109
|
if (debugOverlay)
|
|
1106
1110
|
{
|
|
1107
|
-
overlayContext.fillText(engineName
|
|
1111
|
+
overlayContext.fillText(`${engineName} v${engineVersion}`, x, y += h/2 );
|
|
1108
1112
|
overlayContext.fillText('Time: ' + formatTime(time), x, y += h);
|
|
1109
1113
|
overlayContext.fillText('FPS: ' + averageFPS.toFixed(1), x, y += h);
|
|
1110
1114
|
overlayContext.fillText('Objects: ' + engineObjects.length, x, y += h);
|
|
@@ -1148,7 +1152,7 @@ function debugRender()
|
|
|
1148
1152
|
overlayContext.fillText(debugRaycast ? 'Debug Raycasts' : '', x, y += h);
|
|
1149
1153
|
overlayContext.fillText(debugGamepads ? 'Debug Gamepads' : '', x, y += h);
|
|
1150
1154
|
}
|
|
1151
|
-
|
|
1155
|
+
|
|
1152
1156
|
overlayContext.restore();
|
|
1153
1157
|
}
|
|
1154
1158
|
}
|
|
@@ -1236,7 +1240,7 @@ function debugVideoCaptureUpdate()
|
|
|
1236
1240
|
{
|
|
1237
1241
|
if (!debugVideoCaptureIsActive())
|
|
1238
1242
|
return; // not recording
|
|
1239
|
-
|
|
1243
|
+
|
|
1240
1244
|
// save the video frame
|
|
1241
1245
|
combineCanvases();
|
|
1242
1246
|
debugVideoCaptureTrack.requestFrame();
|
|
@@ -1317,7 +1321,19 @@ function lerp(valueA, valueB, percent)
|
|
|
1317
1321
|
if (valueA >= 0 && valueA <= 1 && ((valueB < 0 || valueB > 1) && (percent < 0 || percent > 1)))
|
|
1318
1322
|
console.warn('lerp() parameter order changed! use lerp(start, end, p)');
|
|
1319
1323
|
return valueA + clamp(percent) * (valueB-valueA);
|
|
1320
|
-
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
/** Gets percent between percentA and percentB and linearly interpolates between lerpA and lerpB
|
|
1327
|
+
* A shortcut for lerp(lerpA, lerpB, percent(value, percentA, percentB))
|
|
1328
|
+
* @param {number} value
|
|
1329
|
+
* @param {number} percentA
|
|
1330
|
+
* @param {number} percentB
|
|
1331
|
+
* @param {number} lerpA
|
|
1332
|
+
* @param {number} lerpB
|
|
1333
|
+
* @return {number}
|
|
1334
|
+
* @memberof Utilities */
|
|
1335
|
+
function percentLerp(value, percentA, percentB, lerpA, lerpB)
|
|
1336
|
+
{ return lerp(lerpA, lerpB, percent(value, percentA, percentB)); }
|
|
1321
1337
|
|
|
1322
1338
|
/** Returns signed wrapped distance between the two values passed in
|
|
1323
1339
|
* @param {number} valueA
|
|
@@ -1369,7 +1385,7 @@ function smoothStep(percent) { return percent * percent * (3 - 2 * percent); }
|
|
|
1369
1385
|
* @memberof Utilities */
|
|
1370
1386
|
function isPowerOfTwo(value) { return !(value & (value - 1)); }
|
|
1371
1387
|
|
|
1372
|
-
/** Returns the nearest power of two not less
|
|
1388
|
+
/** Returns the nearest power of two not less than the value
|
|
1373
1389
|
* @param {number} value
|
|
1374
1390
|
* @return {number}
|
|
1375
1391
|
* @memberof Utilities */
|
|
@@ -1384,8 +1400,8 @@ function nearestPowerOfTwo(value) { return 2**Math.ceil(Math.log2(value)); }
|
|
|
1384
1400
|
* @return {boolean} - True if overlapping
|
|
1385
1401
|
* @memberof Utilities */
|
|
1386
1402
|
function isOverlapping(posA, sizeA, posB, sizeB=vec2())
|
|
1387
|
-
{
|
|
1388
|
-
return abs(posA.x - posB.x)*2 < sizeA.x + sizeB.x
|
|
1403
|
+
{
|
|
1404
|
+
return abs(posA.x - posB.x)*2 < sizeA.x + sizeB.x
|
|
1389
1405
|
&& abs(posA.y - posB.y)*2 < sizeA.y + sizeB.y;
|
|
1390
1406
|
}
|
|
1391
1407
|
|
|
@@ -1440,7 +1456,7 @@ function isIntersecting(start, end, pos, size)
|
|
|
1440
1456
|
function wave(frequency=1, amplitude=1, t=time, offset=0)
|
|
1441
1457
|
{ return amplitude/2 * (1 - Math.cos(offset + t*frequency*2*PI)); }
|
|
1442
1458
|
|
|
1443
|
-
/** Formats seconds to mm:ss style for display purposes
|
|
1459
|
+
/** Formats seconds to mm:ss style for display purposes
|
|
1444
1460
|
* @param {number} t - time in seconds
|
|
1445
1461
|
* @return {string}
|
|
1446
1462
|
* @memberof Utilities */
|
|
@@ -1456,13 +1472,12 @@ async function fetchJSON(url)
|
|
|
1456
1472
|
return response.json();
|
|
1457
1473
|
}
|
|
1458
1474
|
|
|
1459
|
-
/**
|
|
1475
|
+
/**
|
|
1460
1476
|
* Check if object is a valid number, not NaN or undefined, but it may be infinite
|
|
1461
1477
|
* @param {any} n
|
|
1462
1478
|
* @return {boolean}
|
|
1463
|
-
* @memberof Utilities
|
|
1464
|
-
|
|
1465
|
-
function isNumber(n) { return typeof n == 'number' && !isNaN(n); }
|
|
1479
|
+
* @memberof Utilities */
|
|
1480
|
+
function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
|
|
1466
1481
|
|
|
1467
1482
|
///////////////////////////////////////////////////////////////////////////////
|
|
1468
1483
|
|
|
@@ -1517,13 +1532,13 @@ function randInCircle(radius=1, minRadius=0)
|
|
|
1517
1532
|
* @memberof Random */
|
|
1518
1533
|
function randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear=false)
|
|
1519
1534
|
{
|
|
1520
|
-
return linear ? colorA.lerp(colorB, rand()) :
|
|
1535
|
+
return linear ? colorA.lerp(colorB, rand()) :
|
|
1521
1536
|
new Color(rand(colorA.r,colorB.r), rand(colorA.g,colorB.g), rand(colorA.b,colorB.b), rand(colorA.a,colorB.a));
|
|
1522
1537
|
}
|
|
1523
1538
|
|
|
1524
1539
|
///////////////////////////////////////////////////////////////////////////////
|
|
1525
1540
|
|
|
1526
|
-
/**
|
|
1541
|
+
/**
|
|
1527
1542
|
* Seeded random number generator
|
|
1528
1543
|
* - Can be used to create a deterministic random number sequence
|
|
1529
1544
|
* @example
|
|
@@ -1550,8 +1565,8 @@ class RandomGenerator
|
|
|
1550
1565
|
float(valueA=1, valueB=0)
|
|
1551
1566
|
{
|
|
1552
1567
|
// xorshift algorithm
|
|
1553
|
-
this.seed ^= this.seed << 13;
|
|
1554
|
-
this.seed ^= this.seed >>> 17;
|
|
1568
|
+
this.seed ^= this.seed << 13;
|
|
1569
|
+
this.seed ^= this.seed >>> 17;
|
|
1555
1570
|
this.seed ^= this.seed << 5;
|
|
1556
1571
|
return valueB + (valueA - valueB) * ((this.seed >>> 0) / 2**32);
|
|
1557
1572
|
}
|
|
@@ -1600,28 +1615,26 @@ class RandomGenerator
|
|
|
1600
1615
|
* let a = vec2(0, 1); // vector with coordinates (0, 1)
|
|
1601
1616
|
* a = vec2(5); // set a to (5, 5)
|
|
1602
1617
|
* b = vec2(); // set b to (0, 0)
|
|
1603
|
-
* @memberof Utilities
|
|
1604
|
-
*/
|
|
1618
|
+
* @memberof Utilities */
|
|
1605
1619
|
function vec2(x=0, y) { return new Vector2(x, y === undefined ? x : y); }
|
|
1606
1620
|
|
|
1607
|
-
/**
|
|
1621
|
+
/**
|
|
1608
1622
|
* Check if object is a valid Vector2
|
|
1609
1623
|
* @param {any} v
|
|
1610
1624
|
* @return {boolean}
|
|
1611
|
-
* @memberof Utilities
|
|
1612
|
-
|
|
1613
|
-
function isVector2(v) { return v instanceof Vector2; }
|
|
1625
|
+
* @memberof Utilities */
|
|
1626
|
+
function isVector2(v) { return v instanceof Vector2 && v.isValid(); }
|
|
1614
1627
|
|
|
1615
1628
|
// vector2 asserts
|
|
1616
|
-
function ASSERT_VECTOR2_VALID(v) { ASSERT(isVector2(v)
|
|
1629
|
+
function ASSERT_VECTOR2_VALID(v) { ASSERT(isVector2(v), 'Vector2 is invalid.', v); }
|
|
1617
1630
|
function ASSERT_NUMBER_VALID(n) { ASSERT(isNumber(n), 'Number is invalid.', n); }
|
|
1618
1631
|
function ASSERT_VECTOR2_NORMAL(v)
|
|
1619
1632
|
{
|
|
1620
1633
|
ASSERT_VECTOR2_VALID(v);
|
|
1621
|
-
ASSERT(abs(v.lengthSquared()-1) < .01, 'Vector2 is not normal.', v);
|
|
1634
|
+
ASSERT(abs(v.lengthSquared()-1) < .01, 'Vector2 is not normal.', v);
|
|
1622
1635
|
}
|
|
1623
1636
|
|
|
1624
|
-
/**
|
|
1637
|
+
/**
|
|
1625
1638
|
* 2D Vector object with vector math library
|
|
1626
1639
|
* - Functions do not change this so they can be chained together
|
|
1627
1640
|
* @example
|
|
@@ -1745,7 +1758,7 @@ class Vector2
|
|
|
1745
1758
|
* @param {number} [angle]
|
|
1746
1759
|
* @param {number} [length]
|
|
1747
1760
|
* @return {Vector2} */
|
|
1748
|
-
setAngle(angle=0, length=1)
|
|
1761
|
+
setAngle(angle=0, length=1)
|
|
1749
1762
|
{
|
|
1750
1763
|
ASSERT_NUMBER_VALID(angle);
|
|
1751
1764
|
ASSERT_NUMBER_VALID(length);
|
|
@@ -1760,7 +1773,7 @@ class Vector2
|
|
|
1760
1773
|
rotate(angle)
|
|
1761
1774
|
{
|
|
1762
1775
|
ASSERT_NUMBER_VALID(angle);
|
|
1763
|
-
const c = Math.cos(-angle), s = Math.sin(-angle);
|
|
1776
|
+
const c = Math.cos(-angle), s = Math.sin(-angle);
|
|
1764
1777
|
return new Vector2(this.x*c - this.y*s, this.x*s + this.y*c);
|
|
1765
1778
|
}
|
|
1766
1779
|
|
|
@@ -1772,9 +1785,9 @@ class Vector2
|
|
|
1772
1785
|
ASSERT_NUMBER_VALID(direction);
|
|
1773
1786
|
ASSERT_NUMBER_VALID(length);
|
|
1774
1787
|
direction = mod(direction, 4);
|
|
1775
|
-
ASSERT(direction
|
|
1788
|
+
ASSERT(direction===0 || direction===1 || direction===2 || direction===3,
|
|
1776
1789
|
'Vector2.setDirection() direction must be an integer between 0 and 3.');
|
|
1777
|
-
return vec2(direction%2 ? direction-1 ? -length : length : 0,
|
|
1790
|
+
return vec2(direction%2 ? direction-1 ? -length : length : 0,
|
|
1778
1791
|
direction%2 ? 0 : direction ? -length : length);
|
|
1779
1792
|
}
|
|
1780
1793
|
|
|
@@ -1826,7 +1839,7 @@ class Vector2
|
|
|
1826
1839
|
/** Returns this vector expressed as a string
|
|
1827
1840
|
* @param {number} digits - precision to display
|
|
1828
1841
|
* @return {string} */
|
|
1829
|
-
toString(digits=3)
|
|
1842
|
+
toString(digits=3)
|
|
1830
1843
|
{
|
|
1831
1844
|
ASSERT_NUMBER_VALID(digits);
|
|
1832
1845
|
if (debug)
|
|
@@ -1845,7 +1858,7 @@ class Vector2
|
|
|
1845
1858
|
|
|
1846
1859
|
///////////////////////////////////////////////////////////////////////////////
|
|
1847
1860
|
|
|
1848
|
-
/**
|
|
1861
|
+
/**
|
|
1849
1862
|
* Create a color object with RGBA values, white by default
|
|
1850
1863
|
* @param {number} [r=1] - red
|
|
1851
1864
|
* @param {number} [g=1] - green
|
|
@@ -1856,29 +1869,27 @@ class Vector2
|
|
|
1856
1869
|
*/
|
|
1857
1870
|
function rgb(r, g, b, a) { return new Color(r, g, b, a); }
|
|
1858
1871
|
|
|
1859
|
-
/**
|
|
1872
|
+
/**
|
|
1860
1873
|
* Create a color object with HSLA values, white by default
|
|
1861
1874
|
* @param {number} [h=0] - hue
|
|
1862
1875
|
* @param {number} [s=0] - saturation
|
|
1863
1876
|
* @param {number} [l=1] - lightness
|
|
1864
1877
|
* @param {number} [a=1] - alpha
|
|
1865
1878
|
* @return {Color}
|
|
1866
|
-
* @memberof Utilities
|
|
1867
|
-
*/
|
|
1879
|
+
* @memberof Utilities */
|
|
1868
1880
|
function hsl(h, s, l, a) { return new Color().setHSLA(h, s, l, a); }
|
|
1869
1881
|
|
|
1870
|
-
/**
|
|
1882
|
+
/**
|
|
1871
1883
|
* Check if object is a valid Color
|
|
1872
1884
|
* @param {any} c
|
|
1873
1885
|
* @return {boolean}
|
|
1874
|
-
* @memberof Utilities
|
|
1875
|
-
|
|
1876
|
-
function isColor(c) { return c instanceof Color; }
|
|
1886
|
+
* @memberof Utilities */
|
|
1887
|
+
function isColor(c) { return c instanceof Color && c.isValid(); }
|
|
1877
1888
|
|
|
1878
1889
|
// color asserts
|
|
1879
|
-
function ASSERT_COLOR_VALID(c) { ASSERT(isColor(c)
|
|
1890
|
+
function ASSERT_COLOR_VALID(c) { ASSERT(isColor(c), 'Color is invalid.', c); }
|
|
1880
1891
|
|
|
1881
|
-
/**
|
|
1892
|
+
/**
|
|
1882
1893
|
* Color object (red, green, blue, alpha) with some helpful functions
|
|
1883
1894
|
* @example
|
|
1884
1895
|
* let a = new Color; // white
|
|
@@ -1950,7 +1961,7 @@ class Color
|
|
|
1950
1961
|
* @param {number} scale
|
|
1951
1962
|
* @param {number} [alphaScale=scale]
|
|
1952
1963
|
* @return {Color} */
|
|
1953
|
-
scale(scale, alphaScale=scale)
|
|
1964
|
+
scale(scale, alphaScale=scale)
|
|
1954
1965
|
{ return new Color(this.r*scale, this.g*scale, this.b*scale, this.a*alphaScale); }
|
|
1955
1966
|
|
|
1956
1967
|
/** Returns a copy of this color clamped to the valid range between 0 and 1
|
|
@@ -1967,9 +1978,9 @@ class Color
|
|
|
1967
1978
|
ASSERT_NUMBER_VALID(percent);
|
|
1968
1979
|
const p = clamp(percent);
|
|
1969
1980
|
return new Color(
|
|
1970
|
-
c.r*p + this.r*(1-p),
|
|
1971
|
-
c.g*p + this.g*(1-p),
|
|
1972
|
-
c.b*p + this.b*(1-p),
|
|
1981
|
+
c.r*p + this.r*(1-p),
|
|
1982
|
+
c.g*p + this.g*(1-p),
|
|
1983
|
+
c.b*p + this.b*(1-p),
|
|
1973
1984
|
c.a*p + this.a*(1-p));
|
|
1974
1985
|
}
|
|
1975
1986
|
|
|
@@ -2009,15 +2020,15 @@ class Color
|
|
|
2009
2020
|
const minC = min(r, g, b);
|
|
2010
2021
|
const l = (maxC + minC) / 2;
|
|
2011
2022
|
let h = 0, s = 0;
|
|
2012
|
-
if (maxC
|
|
2023
|
+
if (maxC !== minC)
|
|
2013
2024
|
{
|
|
2014
2025
|
let d = maxC - minC;
|
|
2015
2026
|
s = l > .5 ? d / (2 - maxC - minC) : d / (maxC + minC);
|
|
2016
|
-
if (r
|
|
2027
|
+
if (r === maxC)
|
|
2017
2028
|
h = (g - b) / d + (g < b ? 6 : 0);
|
|
2018
|
-
else if (g
|
|
2029
|
+
else if (g === maxC)
|
|
2019
2030
|
h = (b - r) / d + 2;
|
|
2020
|
-
else if (b
|
|
2031
|
+
else if (b === maxC)
|
|
2021
2032
|
h = (r - g) / d + 4;
|
|
2022
2033
|
}
|
|
2023
2034
|
return [h / 6, s, l, a];
|
|
@@ -2027,7 +2038,7 @@ class Color
|
|
|
2027
2038
|
* @param {number} [amount]
|
|
2028
2039
|
* @param {number} [alphaAmount]
|
|
2029
2040
|
* @return {Color} */
|
|
2030
|
-
mutate(amount=.05, alphaAmount=0)
|
|
2041
|
+
mutate(amount=.05, alphaAmount=0)
|
|
2031
2042
|
{
|
|
2032
2043
|
ASSERT_NUMBER_VALID(amount);
|
|
2033
2044
|
ASSERT_NUMBER_VALID(alphaAmount);
|
|
@@ -2043,47 +2054,47 @@ class Color
|
|
|
2043
2054
|
/** Returns this color expressed as a hex color code
|
|
2044
2055
|
* @param {boolean} [useAlpha] - if alpha should be included in result
|
|
2045
2056
|
* @return {string} */
|
|
2046
|
-
toString(useAlpha = true)
|
|
2057
|
+
toString(useAlpha = true)
|
|
2047
2058
|
{
|
|
2048
|
-
ASSERT(typeof useAlpha
|
|
2059
|
+
ASSERT(typeof useAlpha === 'boolean', 'Use alpha boolean is invalid.', useAlpha);
|
|
2049
2060
|
if (debug && !this.isValid())
|
|
2050
2061
|
return `#000`;
|
|
2051
2062
|
const toHex = (c)=> ((c=clamp(c)*255|0)<16 ? '0' : '') + c.toString(16);
|
|
2052
2063
|
return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
|
|
2053
2064
|
}
|
|
2054
|
-
|
|
2065
|
+
|
|
2055
2066
|
/** Set this color from a hex code
|
|
2056
2067
|
* @param {string} hex - html hex code
|
|
2057
2068
|
* @return {Color} */
|
|
2058
2069
|
setHex(hex)
|
|
2059
2070
|
{
|
|
2060
|
-
ASSERT(typeof hex
|
|
2071
|
+
ASSERT(typeof hex === 'string' && hex[0] === '#', 'Color hex code must be a string starting with #');
|
|
2061
2072
|
ASSERT([4,5,7,9].includes(hex.length), 'Invalid hex');
|
|
2062
2073
|
|
|
2063
2074
|
if (hex.length < 6)
|
|
2064
2075
|
{
|
|
2065
2076
|
const fromHex = (c)=> clamp(parseInt(hex[c],16)/15);
|
|
2066
2077
|
this.r = fromHex(1);
|
|
2067
|
-
this.g = fromHex(2)
|
|
2078
|
+
this.g = fromHex(2);
|
|
2068
2079
|
this.b = fromHex(3);
|
|
2069
|
-
this.a = hex.length
|
|
2080
|
+
this.a = hex.length === 5 ? fromHex(4) : 1;
|
|
2070
2081
|
}
|
|
2071
2082
|
else
|
|
2072
2083
|
{
|
|
2073
2084
|
const fromHex = (c)=> clamp(parseInt(hex.slice(c,c+2),16)/255);
|
|
2074
2085
|
this.r = fromHex(1);
|
|
2075
|
-
this.g = fromHex(3)
|
|
2086
|
+
this.g = fromHex(3);
|
|
2076
2087
|
this.b = fromHex(5);
|
|
2077
|
-
this.a = hex.length
|
|
2088
|
+
this.a = hex.length === 9 ? fromHex(7) : 1;
|
|
2078
2089
|
}
|
|
2079
2090
|
|
|
2080
2091
|
ASSERT_COLOR_VALID(this);
|
|
2081
2092
|
return this;
|
|
2082
2093
|
}
|
|
2083
|
-
|
|
2094
|
+
|
|
2084
2095
|
/** Returns this color expressed as 32 bit RGBA value
|
|
2085
2096
|
* @return {number} */
|
|
2086
|
-
rgbaInt()
|
|
2097
|
+
rgbaInt()
|
|
2087
2098
|
{
|
|
2088
2099
|
const r = clamp(this.r)*255|0;
|
|
2089
2100
|
const g = clamp(this.g)*255<<8;
|
|
@@ -2104,7 +2115,7 @@ class Color
|
|
|
2104
2115
|
/** Color - White #ffffff
|
|
2105
2116
|
* @type {Color}
|
|
2106
2117
|
* @memberof Utilities */
|
|
2107
|
-
const WHITE = rgb();
|
|
2118
|
+
const WHITE = rgb();
|
|
2108
2119
|
|
|
2109
2120
|
/** Color - Clear White #ffffff with 0 alpha
|
|
2110
2121
|
* @type {Color}
|
|
@@ -2219,11 +2230,11 @@ class Timer
|
|
|
2219
2230
|
/** Get percentage elapsed based on time it was set to, returns 0 if not set
|
|
2220
2231
|
* @return {number} */
|
|
2221
2232
|
getPercent() { return this.isSet()? 1-percent(this.time - time, 0, this.setTime) : 0; }
|
|
2222
|
-
|
|
2233
|
+
|
|
2223
2234
|
/** Returns this timer expressed as a string
|
|
2224
2235
|
* @return {string} */
|
|
2225
2236
|
toString() { if (debug) { return this.isSet() ? Math.abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; }}
|
|
2226
|
-
|
|
2237
|
+
|
|
2227
2238
|
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
2228
2239
|
* @return {number} */
|
|
2229
2240
|
valueOf() { return this.get(); }
|
|
@@ -2259,7 +2270,7 @@ let cameraScale = 32;
|
|
|
2259
2270
|
// Display settings
|
|
2260
2271
|
|
|
2261
2272
|
/** Enable applying color to tiles when using canvas2d
|
|
2262
|
-
* - This is slower but should be the same as
|
|
2273
|
+
* - This is slower but should be the same as WebGL rendering
|
|
2263
2274
|
* @type {boolean}
|
|
2264
2275
|
* @default
|
|
2265
2276
|
* @memberof Settings */
|
|
@@ -2305,14 +2316,13 @@ let tilesPixelated = true;
|
|
|
2305
2316
|
* @memberof Settings */
|
|
2306
2317
|
let fontDefault = 'arial';
|
|
2307
2318
|
|
|
2308
|
-
/** Enable to show the LittleJS splash screen
|
|
2319
|
+
/** Enable to show the LittleJS splash screen on startup
|
|
2309
2320
|
* @type {boolean}
|
|
2310
2321
|
* @default
|
|
2311
2322
|
* @memberof Settings */
|
|
2312
2323
|
let showSplashScreen = false;
|
|
2313
2324
|
|
|
2314
2325
|
/** Disables all rendering, audio, and input for servers
|
|
2315
|
-
* - Must be set before startup to take effect
|
|
2316
2326
|
* @type {boolean}
|
|
2317
2327
|
* @default
|
|
2318
2328
|
* @memberof Settings */
|
|
@@ -2321,13 +2331,18 @@ let headlessMode = false;
|
|
|
2321
2331
|
///////////////////////////////////////////////////////////////////////////////
|
|
2322
2332
|
// WebGL settings
|
|
2323
2333
|
|
|
2324
|
-
/** Enable
|
|
2325
|
-
* - Must be set before startup to take effect
|
|
2334
|
+
/** Enable WebGL accelerated rendering
|
|
2326
2335
|
* @type {boolean}
|
|
2327
2336
|
* @default
|
|
2328
2337
|
* @memberof Settings */
|
|
2329
2338
|
let glEnable = true;
|
|
2330
2339
|
|
|
2340
|
+
/** How many sided poly to use when drawing circles and ellipses with WebGL
|
|
2341
|
+
* @type {number}
|
|
2342
|
+
* @default
|
|
2343
|
+
* @memberof Settings */
|
|
2344
|
+
let glCircleSides = 32;
|
|
2345
|
+
|
|
2331
2346
|
///////////////////////////////////////////////////////////////////////////////
|
|
2332
2347
|
// Tile sheet settings
|
|
2333
2348
|
|
|
@@ -2409,13 +2424,13 @@ let particleEmitRateScale = 1;
|
|
|
2409
2424
|
* @memberof Settings */
|
|
2410
2425
|
let gamepadsEnable = true;
|
|
2411
2426
|
|
|
2412
|
-
/** If true, the dpad input is also routed to the left analog stick (for better
|
|
2427
|
+
/** If true, the dpad input is also routed to the left analog stick (for better accessibility)
|
|
2413
2428
|
* @type {boolean}
|
|
2414
2429
|
* @default
|
|
2415
2430
|
* @memberof Settings */
|
|
2416
2431
|
let gamepadDirectionEmulateStick = true;
|
|
2417
2432
|
|
|
2418
|
-
/** If true the WASD keys are also routed to the direction keys (for better
|
|
2433
|
+
/** If true the WASD keys are also routed to the direction keys (for better accessibility)
|
|
2419
2434
|
* @type {boolean}
|
|
2420
2435
|
* @default
|
|
2421
2436
|
* @memberof Settings */
|
|
@@ -2423,7 +2438,6 @@ let inputWASDEmulateDirection = true;
|
|
|
2423
2438
|
|
|
2424
2439
|
/** True if touch input is enabled for mobile devices
|
|
2425
2440
|
* - Touch events will be routed to mouse events
|
|
2426
|
-
* - Must be set before startup to take effect
|
|
2427
2441
|
* @type {boolean}
|
|
2428
2442
|
* @default
|
|
2429
2443
|
* @memberof Settings */
|
|
@@ -2431,7 +2445,6 @@ let touchInputEnable = true;
|
|
|
2431
2445
|
|
|
2432
2446
|
/** True if touch gamepad should appear on mobile devices
|
|
2433
2447
|
* - Supports left analog stick, 4 face buttons and start button (button 9)
|
|
2434
|
-
* - Must be set before startup to take effect
|
|
2435
2448
|
* @type {boolean}
|
|
2436
2449
|
* @default
|
|
2437
2450
|
* @memberof Settings */
|
|
@@ -2534,7 +2547,7 @@ function setCameraAngle(angle) { cameraAngle = angle; }
|
|
|
2534
2547
|
function setCameraScale(scale) { cameraScale = scale; }
|
|
2535
2548
|
|
|
2536
2549
|
/** Set if tiles should be colorized when using canvas2d
|
|
2537
|
-
* This can be slower but results should look nearly identical to
|
|
2550
|
+
* This can be slower but results should look nearly identical to WebGL rendering
|
|
2538
2551
|
* It can be enabled/disabled at any time
|
|
2539
2552
|
* Optimized for performance, and will use faster method if color is white or untextured
|
|
2540
2553
|
* @param {boolean} colorTiles
|
|
@@ -2570,8 +2583,8 @@ function setCanvasPixelated(pixelated)
|
|
|
2570
2583
|
* @param {boolean} pixelated
|
|
2571
2584
|
* @memberof Settings */
|
|
2572
2585
|
function setOverlayCanvasPixelated(pixelated)
|
|
2573
|
-
{
|
|
2574
|
-
overlayCanvasPixelated = pixelated;
|
|
2586
|
+
{
|
|
2587
|
+
overlayCanvasPixelated = pixelated;
|
|
2575
2588
|
if (overlayCanvas)
|
|
2576
2589
|
overlayCanvas.style.imageRendering = pixelated ? 'pixelated' : '';
|
|
2577
2590
|
}
|
|
@@ -2586,7 +2599,7 @@ function setTilesPixelated(pixelated) { tilesPixelated = pixelated; }
|
|
|
2586
2599
|
* @memberof Settings */
|
|
2587
2600
|
function setFontDefault(font) { fontDefault = font; }
|
|
2588
2601
|
|
|
2589
|
-
/** Set if the LittleJS splash screen be shown on startup
|
|
2602
|
+
/** Set if the LittleJS splash screen should be shown on startup
|
|
2590
2603
|
* @param {boolean} show
|
|
2591
2604
|
* @memberof Settings */
|
|
2592
2605
|
function setShowSplashScreen(show) { showSplashScreen = show; }
|
|
@@ -2596,16 +2609,21 @@ function setShowSplashScreen(show) { showSplashScreen = show; }
|
|
|
2596
2609
|
* @memberof Settings */
|
|
2597
2610
|
function setHeadlessMode(headless) { headlessMode = headless; }
|
|
2598
2611
|
|
|
2599
|
-
/** Set if
|
|
2612
|
+
/** Set if WebGL rendering is enabled
|
|
2600
2613
|
* @param {boolean} enable
|
|
2601
2614
|
* @memberof Settings */
|
|
2602
2615
|
function setGLEnable(enable)
|
|
2603
2616
|
{
|
|
2604
2617
|
glEnable = enable;
|
|
2605
|
-
if (glCanvas) // hide glCanvas if
|
|
2618
|
+
if (glCanvas) // hide glCanvas if WebGL is disabled
|
|
2606
2619
|
glCanvas.style.visibility = enable ? 'visible' : 'hidden';
|
|
2607
2620
|
}
|
|
2608
2621
|
|
|
2622
|
+
/** Set how many sided polygons to use when drawing circles and elipses with WebGL
|
|
2623
|
+
* @param {number} sides
|
|
2624
|
+
* @memberof Settings */
|
|
2625
|
+
function setGLCircleSides(sides) { glCircleSides = sides; }
|
|
2626
|
+
|
|
2609
2627
|
/** Set default size of tiles in pixels
|
|
2610
2628
|
* @param {Vector2} size
|
|
2611
2629
|
* @memberof Settings */
|
|
@@ -2636,7 +2654,7 @@ function setObjectDefaultDamping(damp) { objectDefaultDamping = damp; }
|
|
|
2636
2654
|
* @memberof Settings */
|
|
2637
2655
|
function setObjectDefaultAngleDamping(damp) { objectDefaultAngleDamping = damp; }
|
|
2638
2656
|
|
|
2639
|
-
/** Set how much to bounce when a collision
|
|
2657
|
+
/** Set how much to bounce when a collision occurs
|
|
2640
2658
|
* @param {number} restitution
|
|
2641
2659
|
* @memberof Settings */
|
|
2642
2660
|
function setObjectDefaultRestitution(restitution) { objectDefaultRestitution = restitution; }
|
|
@@ -2760,11 +2778,11 @@ function setShowWatermark(show) { showWatermark = show; }
|
|
|
2760
2778
|
* @param {string} key
|
|
2761
2779
|
* @memberof Debug */
|
|
2762
2780
|
function setDebugKey(key) { debugKey = key; }
|
|
2763
|
-
/**
|
|
2781
|
+
/**
|
|
2764
2782
|
* LittleJS Object System
|
|
2765
2783
|
*/
|
|
2766
2784
|
|
|
2767
|
-
/**
|
|
2785
|
+
/**
|
|
2768
2786
|
* LittleJS Object Base Object Class
|
|
2769
2787
|
* - Top level object class used by the engine
|
|
2770
2788
|
* - Automatically adds self to object list
|
|
@@ -2787,7 +2805,7 @@ function setDebugKey(key) { debugKey = key; }
|
|
|
2787
2805
|
* @example
|
|
2788
2806
|
* // create an engine object, normally you would first extend the class with your own
|
|
2789
2807
|
* const pos = vec2(2,3);
|
|
2790
|
-
* const object = new EngineObject(pos);
|
|
2808
|
+
* const object = new EngineObject(pos);
|
|
2791
2809
|
*/
|
|
2792
2810
|
class EngineObject
|
|
2793
2811
|
{
|
|
@@ -2802,12 +2820,12 @@ class EngineObject
|
|
|
2802
2820
|
constructor(pos=vec2(), size=vec2(1), tileInfo, angle=0, color=new Color, renderOrder=0)
|
|
2803
2821
|
{
|
|
2804
2822
|
// check passed in params
|
|
2805
|
-
ASSERT(isVector2(pos)
|
|
2806
|
-
ASSERT(isVector2(size)
|
|
2823
|
+
ASSERT(isVector2(pos), 'object pos should be a vec2');
|
|
2824
|
+
ASSERT(isVector2(size), 'object size should be a vec2');
|
|
2807
2825
|
ASSERT(!tileInfo || tileInfo instanceof TileInfo, 'object tileInfo should be a TileInfo or undefined');
|
|
2808
|
-
ASSERT(typeof angle
|
|
2809
|
-
ASSERT(isColor(color)
|
|
2810
|
-
ASSERT(typeof renderOrder
|
|
2826
|
+
ASSERT(typeof angle === 'number' && isFinite(angle), 'object angle should be a number');
|
|
2827
|
+
ASSERT(isColor(color), 'object color should be a valid rgba color');
|
|
2828
|
+
ASSERT(typeof renderOrder === 'number', 'object renderOrder should be a number');
|
|
2811
2829
|
|
|
2812
2830
|
/** @property {Vector2} - World space position of the object */
|
|
2813
2831
|
this.pos = pos.copy();
|
|
@@ -2875,7 +2893,7 @@ class EngineObject
|
|
|
2875
2893
|
// add to list of objects
|
|
2876
2894
|
engineObjects.push(this);
|
|
2877
2895
|
}
|
|
2878
|
-
|
|
2896
|
+
|
|
2879
2897
|
/** Update the object transform, called automatically by engine even when paused */
|
|
2880
2898
|
updateTransforms()
|
|
2881
2899
|
{
|
|
@@ -2944,7 +2962,7 @@ class EngineObject
|
|
|
2944
2962
|
for (const o of engineObjectsCollide)
|
|
2945
2963
|
{
|
|
2946
2964
|
// non solid objects don't collide with each other
|
|
2947
|
-
if (!this.isSolid && !o.isSolid || o.destroyed || o.parent || o
|
|
2965
|
+
if (!this.isSolid && !o.isSolid || o.destroyed || o.parent || o === this)
|
|
2948
2966
|
continue;
|
|
2949
2967
|
|
|
2950
2968
|
// check collision
|
|
@@ -2967,7 +2985,7 @@ class EngineObject
|
|
|
2967
2985
|
this.velocity = this.velocity.add(velocity);
|
|
2968
2986
|
if (o.mass) // push away if not fixed
|
|
2969
2987
|
o.velocity = o.velocity.subtract(velocity);
|
|
2970
|
-
|
|
2988
|
+
|
|
2971
2989
|
debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f00');
|
|
2972
2990
|
continue;
|
|
2973
2991
|
}
|
|
@@ -2978,7 +2996,7 @@ class EngineObject
|
|
|
2978
2996
|
const isBlockedX = abs(oldPos.y - o.pos.y)*2 < sizeBoth.y;
|
|
2979
2997
|
const isBlockedY = abs(oldPos.x - o.pos.x)*2 < sizeBoth.x;
|
|
2980
2998
|
const restitution = max(this.restitution, o.restitution);
|
|
2981
|
-
|
|
2999
|
+
|
|
2982
3000
|
if (smallStepUp || isBlockedY || !isBlockedX) // resolve y collision
|
|
2983
3001
|
{
|
|
2984
3002
|
// push outside object collision
|
|
@@ -3066,7 +3084,7 @@ class EngineObject
|
|
|
3066
3084
|
{
|
|
3067
3085
|
// move to previous position
|
|
3068
3086
|
this.pos.y = oldPos.y;
|
|
3069
|
-
this.groundObject = undefined;
|
|
3087
|
+
this.groundObject = undefined;
|
|
3070
3088
|
}
|
|
3071
3089
|
}
|
|
3072
3090
|
if (blockedLayerX)
|
|
@@ -3080,20 +3098,20 @@ class EngineObject
|
|
|
3080
3098
|
}
|
|
3081
3099
|
}
|
|
3082
3100
|
}
|
|
3083
|
-
|
|
3101
|
+
|
|
3084
3102
|
/** Render the object, draws a tile by default, automatically called each frame, sorted by renderOrder */
|
|
3085
3103
|
render()
|
|
3086
3104
|
{
|
|
3087
3105
|
// default object render
|
|
3088
3106
|
drawTile(this.pos, this.drawSize || this.size, this.tileInfo, this.color, this.angle, this.mirror, this.additiveColor);
|
|
3089
3107
|
}
|
|
3090
|
-
|
|
3108
|
+
|
|
3091
3109
|
/** Destroy this object, destroy its children, detach it's parent, and mark it for removal */
|
|
3092
3110
|
destroy()
|
|
3093
|
-
{
|
|
3111
|
+
{
|
|
3094
3112
|
if (this.destroyed)
|
|
3095
3113
|
return;
|
|
3096
|
-
|
|
3114
|
+
|
|
3097
3115
|
// disconnect from parent and destroy children
|
|
3098
3116
|
this.destroyed = 1;
|
|
3099
3117
|
this.parent && this.parent.removeChild(this);
|
|
@@ -3119,7 +3137,7 @@ class EngineObject
|
|
|
3119
3137
|
/** Convert from world space to local space for a vector (rotation only)
|
|
3120
3138
|
* @param {Vector2} vec - world space vector */
|
|
3121
3139
|
worldToLocalVector(vec) { return vec.rotate(-this.angle); }
|
|
3122
|
-
|
|
3140
|
+
|
|
3123
3141
|
/** Called to check if a tile collision should be resolved
|
|
3124
3142
|
* @param {number} tileData - the value of the tile at the position
|
|
3125
3143
|
* @param {Vector2} pos - tile where the collision occurred
|
|
@@ -3138,16 +3156,19 @@ class EngineObject
|
|
|
3138
3156
|
|
|
3139
3157
|
/** Apply acceleration to this object (adjust velocity, not affected by mass)
|
|
3140
3158
|
* @param {Vector2} acceleration */
|
|
3141
|
-
applyAcceleration(acceleration)
|
|
3159
|
+
applyAcceleration(acceleration)
|
|
3160
|
+
{ if (this.mass) this.velocity = this.velocity.add(acceleration); }
|
|
3142
3161
|
|
|
3143
|
-
/** Apply angular acceleration to this object
|
|
3162
|
+
/** Apply angular acceleration to this object
|
|
3144
3163
|
* @param {number} acceleration */
|
|
3145
|
-
applyAngularAcceleration(acceleration)
|
|
3164
|
+
applyAngularAcceleration(acceleration)
|
|
3165
|
+
{ if (this.mass) this.angleVelocity += acceleration; }
|
|
3146
3166
|
|
|
3147
3167
|
/** Apply force to this object (adjust velocity, affected by mass)
|
|
3148
3168
|
* @param {Vector2} force */
|
|
3149
|
-
applyForce(force)
|
|
3150
|
-
|
|
3169
|
+
applyForce(force)
|
|
3170
|
+
{ if (this.mass) this.applyAcceleration(force.scale(1/this.mass)); }
|
|
3171
|
+
|
|
3151
3172
|
/** Get the direction of the mirror
|
|
3152
3173
|
* @return {number} -1 if this.mirror is true, or 1 if not mirrored */
|
|
3153
3174
|
getMirrorSign() { return this.mirror ? -1 : 1; }
|
|
@@ -3169,7 +3190,7 @@ class EngineObject
|
|
|
3169
3190
|
* @param {EngineObject} child */
|
|
3170
3191
|
removeChild(child)
|
|
3171
3192
|
{
|
|
3172
|
-
ASSERT(child.parent
|
|
3193
|
+
ASSERT(child.parent === this && this.children.includes(child));
|
|
3173
3194
|
this.children.splice(this.children.indexOf(child), 1);
|
|
3174
3195
|
child.parent = 0;
|
|
3175
3196
|
}
|
|
@@ -3215,7 +3236,7 @@ class EngineObject
|
|
|
3215
3236
|
{
|
|
3216
3237
|
if (!debug)
|
|
3217
3238
|
return;
|
|
3218
|
-
|
|
3239
|
+
|
|
3219
3240
|
// show object info for debugging
|
|
3220
3241
|
const size = vec2(max(this.size.x, .2), max(this.size.y, .2));
|
|
3221
3242
|
const color = rgb(this.collideTiles?1:0, this.collideSolidObjects?1:0, this.isSolid?1:0, .5);
|
|
@@ -3225,24 +3246,24 @@ class EngineObject
|
|
|
3225
3246
|
this.parent && drawLine(this.pos, this.parent.pos, .1, rgb(1,1,1,.5));
|
|
3226
3247
|
}
|
|
3227
3248
|
}
|
|
3228
|
-
/**
|
|
3249
|
+
/**
|
|
3229
3250
|
* LittleJS Drawing System
|
|
3230
3251
|
* - Hybrid system with both Canvas2D and WebGL available
|
|
3231
3252
|
* - Super fast tile sheet rendering with WebGL
|
|
3232
3253
|
* - Can apply rotation, mirror, color and additive color
|
|
3233
3254
|
* - Font rendering system with built in engine font
|
|
3234
3255
|
* - Many useful utility functions
|
|
3235
|
-
*
|
|
3256
|
+
*
|
|
3236
3257
|
* LittleJS uses a hybrid rendering solution with the best of both Canvas2D and WebGL.
|
|
3237
3258
|
* There are 3 canvas/contexts available to draw to...
|
|
3238
3259
|
* mainCanvas - 2D background canvas, non WebGL stuff like tile layers are drawn here.
|
|
3239
3260
|
* glCanvas - Used by the accelerated WebGL batch rendering system.
|
|
3240
3261
|
* overlayCanvas - Another 2D canvas that appears on top of the other 2 canvases.
|
|
3241
|
-
*
|
|
3262
|
+
*
|
|
3242
3263
|
* The WebGL rendering system is very fast with some caveats...
|
|
3243
3264
|
* - Switching blend modes (additive) or textures causes another draw call which is expensive in excess
|
|
3244
3265
|
* - Group additive rendering together using renderOrder to mitigate this issue
|
|
3245
|
-
*
|
|
3266
|
+
*
|
|
3246
3267
|
* The LittleJS rendering solution is intentionally simple, feel free to adjust it for your needs!
|
|
3247
3268
|
* @namespace Draw
|
|
3248
3269
|
*/
|
|
@@ -3287,7 +3308,7 @@ let workCanvas;
|
|
|
3287
3308
|
* @memberof Draw */
|
|
3288
3309
|
let workContext;
|
|
3289
3310
|
|
|
3290
|
-
/** The size of the main canvas (and other secondary canvases)
|
|
3311
|
+
/** The size of the main canvas (and other secondary canvases)
|
|
3291
3312
|
* @type {Vector2}
|
|
3292
3313
|
* @memberof Draw */
|
|
3293
3314
|
let mainCanvasSize = vec2();
|
|
@@ -3297,12 +3318,14 @@ let mainCanvasSize = vec2();
|
|
|
3297
3318
|
* @memberof Draw */
|
|
3298
3319
|
let textureInfos = [];
|
|
3299
3320
|
|
|
3300
|
-
|
|
3321
|
+
/** Keeps track of how many draw calls there were each frame for debugging
|
|
3322
|
+
* @type {number}
|
|
3323
|
+
* @memberof Draw */
|
|
3301
3324
|
let drawCount;
|
|
3302
3325
|
|
|
3303
3326
|
///////////////////////////////////////////////////////////////////////////////
|
|
3304
3327
|
|
|
3305
|
-
/**
|
|
3328
|
+
/**
|
|
3306
3329
|
* Create a tile info object using a grid based system
|
|
3307
3330
|
* - This can take vecs or floats for easier use and conversion
|
|
3308
3331
|
* - If an index is passed in, the tile size and index will determine the position
|
|
@@ -3316,15 +3339,14 @@ let drawCount;
|
|
|
3316
3339
|
* tile(5, 8) // a tile at index 5 using a tile size of 8
|
|
3317
3340
|
* tile(1, 16, 3) // a tile at index 1 of size 16 on texture 3
|
|
3318
3341
|
* tile(vec2(4,8), vec2(30,10)) // a tile at index (4,8) with a size of (30,10)
|
|
3319
|
-
* @memberof Draw
|
|
3320
|
-
*/
|
|
3342
|
+
* @memberof Draw */
|
|
3321
3343
|
function tile(pos=new Vector2, size=tileSizeDefault, textureIndex=0, padding=0)
|
|
3322
3344
|
{
|
|
3323
3345
|
if (headlessMode)
|
|
3324
3346
|
return new TileInfo;
|
|
3325
3347
|
|
|
3326
3348
|
// if size is a number, make it a vector
|
|
3327
|
-
if (typeof size
|
|
3349
|
+
if (typeof size === 'number')
|
|
3328
3350
|
{
|
|
3329
3351
|
ASSERT(size > 0);
|
|
3330
3352
|
size = new Vector2(size, size);
|
|
@@ -3333,24 +3355,24 @@ function tile(pos=new Vector2, size=tileSizeDefault, textureIndex=0, padding=0)
|
|
|
3333
3355
|
// create tile info object
|
|
3334
3356
|
const tileInfo = new TileInfo(new Vector2, size, textureIndex, padding);
|
|
3335
3357
|
|
|
3336
|
-
//
|
|
3358
|
+
// get the position of the tile
|
|
3337
3359
|
const textureInfo = textureInfos[textureIndex];
|
|
3338
3360
|
ASSERT(!!textureInfo, 'Texture not loaded');
|
|
3339
3361
|
const sizePaddedX = size.x + padding*2;
|
|
3340
3362
|
const sizePaddedY = size.y + padding*2;
|
|
3341
|
-
if (typeof pos
|
|
3363
|
+
if (typeof pos === 'number')
|
|
3342
3364
|
{
|
|
3343
3365
|
const cols = textureInfo.size.x / sizePaddedX |0;
|
|
3344
|
-
ASSERT(cols>0, 'Tile size is too big for texture');
|
|
3366
|
+
ASSERT(cols > 0, 'Tile size is too big for texture');
|
|
3345
3367
|
const posX = pos % cols, posY = (pos / cols) |0;
|
|
3346
3368
|
tileInfo.pos.set(posX*sizePaddedX+padding, posY*sizePaddedY+padding);
|
|
3347
3369
|
}
|
|
3348
3370
|
else
|
|
3349
3371
|
tileInfo.pos.set(pos.x*sizePaddedX+padding, pos.y*sizePaddedY+padding);
|
|
3350
|
-
return tileInfo;
|
|
3372
|
+
return tileInfo;
|
|
3351
3373
|
}
|
|
3352
3374
|
|
|
3353
|
-
/**
|
|
3375
|
+
/**
|
|
3354
3376
|
* Tile Info - Stores info about how to draw a tile
|
|
3355
3377
|
*/
|
|
3356
3378
|
class TileInfo
|
|
@@ -3388,14 +3410,14 @@ class TileInfo
|
|
|
3388
3410
|
*/
|
|
3389
3411
|
frame(frame)
|
|
3390
3412
|
{
|
|
3391
|
-
ASSERT(typeof frame
|
|
3413
|
+
ASSERT(typeof frame === 'number');
|
|
3392
3414
|
return this.offset(new Vector2(frame*(this.size.x+this.padding*2), 0));
|
|
3393
3415
|
}
|
|
3394
3416
|
|
|
3395
3417
|
/**
|
|
3396
3418
|
* Set this tile to use a full image
|
|
3397
3419
|
* @param {HTMLImageElement|OffscreenCanvas} image
|
|
3398
|
-
* @param {WebGLTexture} [glTexture] -
|
|
3420
|
+
* @param {WebGLTexture} [glTexture] - WebGL texture
|
|
3399
3421
|
* @return {TileInfo}
|
|
3400
3422
|
*/
|
|
3401
3423
|
setFullImage(image, glTexture)
|
|
@@ -3413,7 +3435,7 @@ class TextureInfo
|
|
|
3413
3435
|
/**
|
|
3414
3436
|
* Create a TextureInfo, called automatically by the engine
|
|
3415
3437
|
* @param {HTMLImageElement|OffscreenCanvas} image
|
|
3416
|
-
* @param {WebGLTexture} [glTexture] -
|
|
3438
|
+
* @param {WebGLTexture} [glTexture] - WebGL texture
|
|
3417
3439
|
*/
|
|
3418
3440
|
constructor(image, glTexture)
|
|
3419
3441
|
{
|
|
@@ -3423,7 +3445,7 @@ class TextureInfo
|
|
|
3423
3445
|
this.size = vec2(image.width, image.height);
|
|
3424
3446
|
/** @property {Vector2} - inverse of the size, cached for rendering */
|
|
3425
3447
|
this.sizeInverse = vec2(1/image.width, 1/image.height);
|
|
3426
|
-
/** @property {WebGLTexture} -
|
|
3448
|
+
/** @property {WebGLTexture} - WebGL texture */
|
|
3427
3449
|
this.glTexture = glTexture;
|
|
3428
3450
|
}
|
|
3429
3451
|
|
|
@@ -3439,28 +3461,25 @@ class TextureInfo
|
|
|
3439
3461
|
// Drawing functions
|
|
3440
3462
|
|
|
3441
3463
|
/** Draw textured tile centered in world space, with color applied if using WebGL
|
|
3442
|
-
* @param {Vector2}
|
|
3443
|
-
* @param {Vector2}
|
|
3444
|
-
* @param {TileInfo}[tileInfo]
|
|
3445
|
-
* @param {Color}
|
|
3446
|
-
* @param {number}
|
|
3447
|
-
* @param {boolean}
|
|
3448
|
-
* @param {Color}
|
|
3449
|
-
* @param {boolean}
|
|
3450
|
-
* @param {boolean}
|
|
3464
|
+
* @param {Vector2} pos - Center of the tile in world space
|
|
3465
|
+
* @param {Vector2} [size=(1,1)] - Size of the tile in world space
|
|
3466
|
+
* @param {TileInfo} [tileInfo] - Tile info to use, untextured if undefined
|
|
3467
|
+
* @param {Color} [color=(1,1,1,1)] - Color to modulate with
|
|
3468
|
+
* @param {number} [angle] - Angle to rotate by
|
|
3469
|
+
* @param {boolean} [mirror] - Is image flipped along the Y axis?
|
|
3470
|
+
* @param {Color} [additiveColor] - Additive color to be applied if any
|
|
3471
|
+
* @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering?
|
|
3472
|
+
* @param {boolean} [screenSpace=false] - Are the pos and size are in screen space?
|
|
3451
3473
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
3452
3474
|
* @memberof Draw */
|
|
3453
|
-
function drawTile(pos, size=new Vector2(1), tileInfo, color=
|
|
3475
|
+
function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
|
|
3454
3476
|
angle=0, mirror, additiveColor, useWebGL=glEnable, screenSpace, context)
|
|
3455
3477
|
{
|
|
3456
|
-
ASSERT(
|
|
3457
|
-
ASSERT(isVector2(
|
|
3458
|
-
ASSERT(isVector2(size) && size.isValid(), 'drawTile size should be a vec2');
|
|
3478
|
+
ASSERT(isVector2(pos), 'drawTile pos should be a vec2');
|
|
3479
|
+
ASSERT(isVector2(size), 'drawTile size should be a vec2');
|
|
3459
3480
|
ASSERT(isColor(color) && (!additiveColor || isColor(additiveColor)), 'drawTile color is invalid');
|
|
3460
3481
|
ASSERT(isNumber(angle), 'drawTile angle should be a number');
|
|
3461
|
-
|
|
3462
|
-
if (color.a <= 0 && (!additiveColor || additiveColor.a <= 0) || !size.x || !size.y)
|
|
3463
|
-
return; // completely invisible, skip render
|
|
3482
|
+
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
3464
3483
|
|
|
3465
3484
|
const textureInfo = tileInfo && tileInfo.textureInfo;
|
|
3466
3485
|
if (useWebGL)
|
|
@@ -3484,28 +3503,28 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=new Color,
|
|
|
3484
3503
|
{
|
|
3485
3504
|
const tileImageFixBleedX = sizeInverse.x*tileFixBleedScale;
|
|
3486
3505
|
const tileImageFixBleedY = sizeInverse.y*tileFixBleedScale;
|
|
3487
|
-
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
3488
|
-
x + tileImageFixBleedX, y + tileImageFixBleedY,
|
|
3489
|
-
x - tileImageFixBleedX + w, y - tileImageFixBleedY + h,
|
|
3490
|
-
color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
|
|
3506
|
+
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
3507
|
+
x + tileImageFixBleedX, y + tileImageFixBleedY,
|
|
3508
|
+
x - tileImageFixBleedX + w, y - tileImageFixBleedY + h,
|
|
3509
|
+
color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
|
|
3491
3510
|
}
|
|
3492
3511
|
else
|
|
3493
3512
|
{
|
|
3494
|
-
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
3495
|
-
x, y, x + w, y + h,
|
|
3496
|
-
color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
|
|
3513
|
+
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
3514
|
+
x, y, x + w, y + h,
|
|
3515
|
+
color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
|
|
3497
3516
|
}
|
|
3498
3517
|
}
|
|
3499
3518
|
else
|
|
3500
3519
|
{
|
|
3501
3520
|
// if no tile info, force untextured
|
|
3502
|
-
glDraw(pos.x, pos.y, size.x, size.y, angle, 0, 0, 0, 0, 0, color.rgbaInt());
|
|
3521
|
+
glDraw(pos.x, pos.y, size.x, size.y, angle, 0, 0, 0, 0, 0, color.rgbaInt());
|
|
3503
3522
|
}
|
|
3504
3523
|
}
|
|
3505
3524
|
else
|
|
3506
3525
|
{
|
|
3507
3526
|
// normal canvas 2D rendering method (slower)
|
|
3508
|
-
|
|
3527
|
+
++drawCount;
|
|
3509
3528
|
size = new Vector2(size.x, -size.y); // fix upside down sprites
|
|
3510
3529
|
drawCanvas2D(pos, size, angle, mirror, (context)=>
|
|
3511
3530
|
{
|
|
@@ -3537,14 +3556,128 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=new Color,
|
|
|
3537
3556
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
3538
3557
|
* @memberof Draw */
|
|
3539
3558
|
function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
3540
|
-
{
|
|
3541
|
-
drawTile(pos, size, undefined, color, angle, false, undefined, useWebGL, screenSpace, context);
|
|
3559
|
+
{
|
|
3560
|
+
drawTile(pos, size, undefined, color, angle, false, undefined, useWebGL, screenSpace, context);
|
|
3561
|
+
}
|
|
3562
|
+
|
|
3563
|
+
/** Draw a rect centered on pos with a gradient from top to bottom
|
|
3564
|
+
* @param {Vector2} pos
|
|
3565
|
+
* @param {Vector2} [size=(1,1)]
|
|
3566
|
+
* @param {Color} [colorTop=(1,1,1,1)]
|
|
3567
|
+
* @param {Color} [colorBottom=(0,0,0,1)]
|
|
3568
|
+
* @param {number} [angle]
|
|
3569
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
3570
|
+
* @param {boolean} [screenSpace]
|
|
3571
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
3572
|
+
* @memberof Draw */
|
|
3573
|
+
function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0, useWebGL=glEnable, screenSpace=false, context)
|
|
3574
|
+
{
|
|
3575
|
+
ASSERT(isVector2(pos), 'drawRectGradient pos should be a vec2');
|
|
3576
|
+
ASSERT(isVector2(size), 'drawRectGradient size should be a vec2');
|
|
3577
|
+
ASSERT(isColor(colorTop) && isColor(colorBottom), 'drawRectGradient color is invalid');
|
|
3578
|
+
ASSERT(isNumber(angle), 'drawRectGradient angle should be a number');
|
|
3579
|
+
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
3580
|
+
if (useWebGL)
|
|
3581
|
+
{
|
|
3582
|
+
if (screenSpace)
|
|
3583
|
+
{
|
|
3584
|
+
// convert to world space
|
|
3585
|
+
pos = screenToWorld(pos);
|
|
3586
|
+
size = size.scale(1/cameraScale);
|
|
3587
|
+
}
|
|
3588
|
+
// build 4 corner points for the rectangle
|
|
3589
|
+
const points = [], colors = [];
|
|
3590
|
+
const halfSizeX = size.x/2, halfSizeY = size.y/2;
|
|
3591
|
+
const colorTopInt = colorTop.rgbaInt();
|
|
3592
|
+
const colorBottomInt = colorBottom.rgbaInt();
|
|
3593
|
+
const c = Math.cos(-angle), s = Math.sin(-angle);
|
|
3594
|
+
for (let i=4; i--;)
|
|
3595
|
+
{
|
|
3596
|
+
const x = i & 1 ? halfSizeX : -halfSizeX;
|
|
3597
|
+
const y = i & 2 ? halfSizeY : -halfSizeY;
|
|
3598
|
+
const rx = x * c - y * s;
|
|
3599
|
+
const ry = x * s + y * c;
|
|
3600
|
+
const color = i & 2 ? colorTopInt : colorBottomInt;
|
|
3601
|
+
points.push(vec2(pos.x + rx, pos.y + ry));
|
|
3602
|
+
colors.push(color);
|
|
3603
|
+
}
|
|
3604
|
+
glDrawColoredPoints(points, colors);
|
|
3605
|
+
}
|
|
3606
|
+
else
|
|
3607
|
+
{
|
|
3608
|
+
// normal canvas 2D rendering method (slower)
|
|
3609
|
+
++drawCount;
|
|
3610
|
+
size = new Vector2(size.x, -size.y); // fix upside down sprites
|
|
3611
|
+
drawCanvas2D(pos, size, angle, false, (context)=>
|
|
3612
|
+
{
|
|
3613
|
+
// if no tile info, use untextured rect
|
|
3614
|
+
const gradient = context.createLinearGradient(0, -.5, 0, .5);
|
|
3615
|
+
gradient.addColorStop(0, colorTop.toString());
|
|
3616
|
+
gradient.addColorStop(1, colorBottom.toString());
|
|
3617
|
+
context.fillStyle = gradient;
|
|
3618
|
+
context.fillRect(-.5, -.5, 1, 1);
|
|
3619
|
+
}, screenSpace, context);
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
|
|
3623
|
+
/** Draw connected lines between a series of points
|
|
3624
|
+
* @param {Array<Vector2>} points
|
|
3625
|
+
* @param {number} [width]
|
|
3626
|
+
* @param {Color} [color=(1,1,1,1)]
|
|
3627
|
+
* @param {boolean} [wrap] - Should the last point connect to the first?
|
|
3628
|
+
* @param {Vector2} [pos=(0,0)] - Offset to apply
|
|
3629
|
+
* @param {number} [angle] - Angle to rotate by
|
|
3630
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
3631
|
+
* @param {boolean} [screenSpace]
|
|
3632
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
3633
|
+
* @memberof Draw */
|
|
3634
|
+
function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0, useWebGL=glEnable, screenSpace, context)
|
|
3635
|
+
{
|
|
3636
|
+
ASSERT(Array.isArray(points), 'drawLineList points should be an array');
|
|
3637
|
+
ASSERT(isNumber(width), 'drawLineList width should be a number');
|
|
3638
|
+
ASSERT(isColor(color), 'drawLineList color is invalid');
|
|
3639
|
+
ASSERT(isVector2(pos), 'drawLineList pos should be a vec2');
|
|
3640
|
+
ASSERT(isNumber(angle), 'drawLineList angle should be a number');
|
|
3641
|
+
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
3642
|
+
if (useWebGL)
|
|
3643
|
+
{
|
|
3644
|
+
let scale = 1;
|
|
3645
|
+
if (screenSpace)
|
|
3646
|
+
{
|
|
3647
|
+
// convert to world space
|
|
3648
|
+
pos = screenToWorld(pos);
|
|
3649
|
+
scale = 1/cameraScale;
|
|
3650
|
+
}
|
|
3651
|
+
glDrawOutlineTransform(points, color.rgbaInt(), width, pos.x, pos.y, scale, scale, angle, wrap);
|
|
3652
|
+
}
|
|
3653
|
+
else
|
|
3654
|
+
{
|
|
3655
|
+
// normal canvas 2D rendering method (slower)
|
|
3656
|
+
++drawCount;
|
|
3657
|
+
drawCanvas2D(pos, vec2(1), angle, false, (context)=>
|
|
3658
|
+
{
|
|
3659
|
+
context.strokeStyle = color.toString();
|
|
3660
|
+
context.lineWidth = width;
|
|
3661
|
+
context.beginPath();
|
|
3662
|
+
for (let i=0; i<points.length; ++i)
|
|
3663
|
+
{
|
|
3664
|
+
const point = points[i];
|
|
3665
|
+
if (i)
|
|
3666
|
+
context.lineTo(point.x, point.y);
|
|
3667
|
+
else
|
|
3668
|
+
context.moveTo(point.x, point.y);
|
|
3669
|
+
}
|
|
3670
|
+
if (wrap)
|
|
3671
|
+
context.closePath();
|
|
3672
|
+
context.stroke();
|
|
3673
|
+
}, screenSpace, context);
|
|
3674
|
+
}
|
|
3542
3675
|
}
|
|
3543
3676
|
|
|
3544
3677
|
/** Draw colored line between two points
|
|
3545
3678
|
* @param {Vector2} posA
|
|
3546
3679
|
* @param {Vector2} posB
|
|
3547
|
-
* @param {number} [
|
|
3680
|
+
* @param {number} [width]
|
|
3548
3681
|
* @param {Color} [color=(1,1,1,1)]
|
|
3549
3682
|
* @param {Vector2} [pos=(0,0)] - Offset to apply
|
|
3550
3683
|
* @param {number} [angle] - Angle to rotate by
|
|
@@ -3552,15 +3685,43 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
|
3552
3685
|
* @param {boolean} [screenSpace]
|
|
3553
3686
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
3554
3687
|
* @memberof Draw */
|
|
3555
|
-
function drawLine(posA, posB,
|
|
3688
|
+
function drawLine(posA, posB, width=.1, color, pos=vec2(), angle=0, useWebGL, screenSpace, context)
|
|
3556
3689
|
{
|
|
3557
3690
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
3558
|
-
const size = vec2(
|
|
3691
|
+
const size = vec2(width, halfDelta.length()*2);
|
|
3559
3692
|
pos = pos.add(posA.add(halfDelta));
|
|
3560
3693
|
angle += halfDelta.angle();
|
|
3561
3694
|
drawRect(pos, size, color, angle, useWebGL, screenSpace, context);
|
|
3562
3695
|
}
|
|
3563
3696
|
|
|
3697
|
+
/** Draw colored regular polygon using passed in number of sides
|
|
3698
|
+
* @param {Vector2} pos
|
|
3699
|
+
* @param {Vector2} [size=(1,1)]
|
|
3700
|
+
* @param {number} [sides]
|
|
3701
|
+
* @param {Color} [color=(1,1,1,1)]
|
|
3702
|
+
* @param {number} [angle]
|
|
3703
|
+
* @param {number} [lineWidth]
|
|
3704
|
+
* @param {Color} [lineColor=(0,0,0,1)]
|
|
3705
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
3706
|
+
* @param {boolean} [screenSpace]
|
|
3707
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
3708
|
+
* @memberof Draw */
|
|
3709
|
+
function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, lineColor=BLACK, angle=0, useWebGL=glEnable, screenSpace=false, context)
|
|
3710
|
+
{
|
|
3711
|
+
ASSERT(isVector2(size), 'drawRegularPoly size should be a vec2');
|
|
3712
|
+
ASSERT(isNumber(sides), 'drawRegularPoly sides should be a number');
|
|
3713
|
+
|
|
3714
|
+
// build regular polygon points
|
|
3715
|
+
const points = [];
|
|
3716
|
+
const sizeX = size.x/2, sizeY = size.y/2;
|
|
3717
|
+
for (let i=sides; i--;)
|
|
3718
|
+
{
|
|
3719
|
+
const a = (i/sides)*PI*2;
|
|
3720
|
+
points.push(vec2(Math.sin(a)*sizeX, Math.cos(a)*sizeY));
|
|
3721
|
+
}
|
|
3722
|
+
drawPoly(points, color, lineWidth, lineColor, pos, angle, useWebGL, screenSpace, context);
|
|
3723
|
+
}
|
|
3724
|
+
|
|
3564
3725
|
/** Draw colored polygon using passed in points
|
|
3565
3726
|
* @param {Array<Vector2>} points - Array of Vector2 points
|
|
3566
3727
|
* @param {Color} [color=(1,1,1,1)]
|
|
@@ -3568,82 +3729,110 @@ function drawLine(posA, posB, thickness=.1, color, pos=vec2(), angle=0, useWebGL
|
|
|
3568
3729
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
3569
3730
|
* @param {Vector2} [pos=(0,0)] - Offset to apply
|
|
3570
3731
|
* @param {number} [angle] - Angle to rotate by
|
|
3571
|
-
* @param {boolean} [useWebGL]
|
|
3732
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
3572
3733
|
* @param {boolean} [screenSpace]
|
|
3573
|
-
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context
|
|
3734
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
3574
3735
|
* @memberof Draw */
|
|
3575
|
-
function drawPoly(points, color=
|
|
3736
|
+
function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(), angle=0, useWebGL=glEnable, screenSpace=false, context=undefined)
|
|
3576
3737
|
{
|
|
3577
|
-
ASSERT(isVector2(pos)
|
|
3738
|
+
ASSERT(isVector2(pos), 'drawPoly pos should be a vec2');
|
|
3578
3739
|
ASSERT(Array.isArray(points), 'drawPoly points should be an array');
|
|
3579
3740
|
ASSERT(isColor(color) && isColor(lineColor), 'drawPoly color is invalid');
|
|
3580
3741
|
ASSERT(isNumber(lineWidth), 'drawPoly lineWidth should be a number');
|
|
3581
3742
|
ASSERT(isNumber(angle), 'drawPoly angle should be a number');
|
|
3582
|
-
ASSERT(!useWebGL, '
|
|
3583
|
-
|
|
3743
|
+
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
3744
|
+
if (useWebGL)
|
|
3584
3745
|
{
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
context.lineTo(point.x, point.y);
|
|
3588
|
-
context.closePath();
|
|
3589
|
-
context.fillStyle = color.toString();
|
|
3590
|
-
context.fill();
|
|
3591
|
-
if (lineWidth)
|
|
3746
|
+
let scale = 1;
|
|
3747
|
+
if (screenSpace)
|
|
3592
3748
|
{
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3749
|
+
// convert to world space
|
|
3750
|
+
pos = screenToWorld(pos);
|
|
3751
|
+
scale = 1/cameraScale;
|
|
3596
3752
|
}
|
|
3597
|
-
|
|
3753
|
+
glDrawPointsTransform(points, color.rgbaInt(), pos.x, pos.y, scale, scale, angle);
|
|
3754
|
+
if (lineWidth > 0)
|
|
3755
|
+
glDrawOutlineTransform(points, lineColor.rgbaInt(), lineWidth, pos.x, pos.y, scale, scale, angle);
|
|
3756
|
+
}
|
|
3757
|
+
else
|
|
3758
|
+
{
|
|
3759
|
+
drawCanvas2D(pos, vec2(1), angle, false, context=>
|
|
3760
|
+
{
|
|
3761
|
+
context.fillStyle = color.toString();
|
|
3762
|
+
context.beginPath();
|
|
3763
|
+
for (const point of points)
|
|
3764
|
+
context.lineTo(point.x, point.y);
|
|
3765
|
+
context.closePath();
|
|
3766
|
+
context.fill();
|
|
3767
|
+
if (lineWidth)
|
|
3768
|
+
{
|
|
3769
|
+
context.strokeStyle = lineColor.toString();
|
|
3770
|
+
context.lineWidth = lineWidth;
|
|
3771
|
+
context.stroke();
|
|
3772
|
+
}
|
|
3773
|
+
}, screenSpace, context);
|
|
3774
|
+
}
|
|
3598
3775
|
}
|
|
3599
3776
|
|
|
3600
3777
|
/** Draw colored ellipse using passed in point
|
|
3601
3778
|
* @param {Vector2} pos
|
|
3602
|
-
* @param {Vector2} [size=(1,1)]
|
|
3779
|
+
* @param {Vector2} [size=(1,1)] - Width and height diameter
|
|
3603
3780
|
* @param {Color} [color=(1,1,1,1)]
|
|
3604
3781
|
* @param {number} [angle]
|
|
3605
3782
|
* @param {number} [lineWidth]
|
|
3606
3783
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
3607
|
-
* @param {boolean} [useWebGL]
|
|
3784
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
3608
3785
|
* @param {boolean} [screenSpace]
|
|
3609
|
-
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context
|
|
3786
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
3610
3787
|
* @memberof Draw */
|
|
3611
|
-
function drawEllipse(pos, size=vec2(1), color=
|
|
3788
|
+
function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineColor=BLACK, useWebGL=glEnable, screenSpace=false, context)
|
|
3612
3789
|
{
|
|
3613
|
-
ASSERT(isVector2(pos)
|
|
3614
|
-
ASSERT(isVector2(size)
|
|
3790
|
+
ASSERT(isVector2(pos), 'drawEllipse pos should be a vec2');
|
|
3791
|
+
ASSERT(isVector2(size), 'drawEllipse size should be a vec2');
|
|
3615
3792
|
ASSERT(isColor(color) && isColor(lineColor), 'drawEllipse color is invalid');
|
|
3616
3793
|
ASSERT(isNumber(angle), 'drawEllipse angle should be a number');
|
|
3617
3794
|
ASSERT(isNumber(lineWidth), 'drawEllipse lineWidth should be a number');
|
|
3618
|
-
ASSERT(lineWidth>=0 && lineWidth < size.x && lineWidth < size.y, 'drawEllipse invalid lineWidth');
|
|
3619
|
-
ASSERT(!useWebGL, '
|
|
3620
|
-
|
|
3795
|
+
ASSERT(lineWidth >= 0 && lineWidth < size.x && lineWidth < size.y, 'drawEllipse invalid lineWidth');
|
|
3796
|
+
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
3797
|
+
if (useWebGL)
|
|
3621
3798
|
{
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3799
|
+
// draw as a regular polygon
|
|
3800
|
+
const sides = glCircleSides;
|
|
3801
|
+
drawRegularPoly(pos, size, sides, color, lineWidth, lineColor, angle, useWebGL, screenSpace, context);
|
|
3802
|
+
}
|
|
3803
|
+
else
|
|
3804
|
+
{
|
|
3805
|
+
drawCanvas2D(pos, vec2(1), angle, false, context=>
|
|
3627
3806
|
{
|
|
3628
|
-
context.
|
|
3629
|
-
context.
|
|
3630
|
-
context.
|
|
3631
|
-
|
|
3632
|
-
|
|
3807
|
+
context.fillStyle = color.toString();
|
|
3808
|
+
context.beginPath();
|
|
3809
|
+
context.ellipse(0, 0, size.x/2, size.y/2, 0, 0, 9);
|
|
3810
|
+
context.fill();
|
|
3811
|
+
if (lineWidth)
|
|
3812
|
+
{
|
|
3813
|
+
context.strokeStyle = lineColor.toString();
|
|
3814
|
+
context.lineWidth = lineWidth;
|
|
3815
|
+
context.stroke();
|
|
3816
|
+
}
|
|
3817
|
+
}, screenSpace, context);
|
|
3818
|
+
}
|
|
3633
3819
|
}
|
|
3634
3820
|
|
|
3635
3821
|
/** Draw colored circle using passed in point
|
|
3636
3822
|
* @param {Vector2} pos
|
|
3637
|
-
* @param {number} [
|
|
3823
|
+
* @param {number} [size=1] - Diameter
|
|
3638
3824
|
* @param {Color} [color=(1,1,1,1)]
|
|
3639
3825
|
* @param {number} [lineWidth=0]
|
|
3640
3826
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
3641
|
-
* @param {boolean} [useWebGL]
|
|
3642
|
-
* @param {boolean} [screenSpace
|
|
3643
|
-
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context
|
|
3827
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
3828
|
+
* @param {boolean} [screenSpace]
|
|
3829
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
3644
3830
|
* @memberof Draw */
|
|
3645
|
-
function drawCircle(pos,
|
|
3646
|
-
{
|
|
3831
|
+
function drawCircle(pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, useWebGL=glEnable, screenSpace=false, context)
|
|
3832
|
+
{
|
|
3833
|
+
ASSERT(isNumber(size), 'drawCircle size should be a number');
|
|
3834
|
+
drawEllipse(pos, vec2(size), color, 0, lineWidth, lineColor, useWebGL, screenSpace, context);
|
|
3835
|
+
}
|
|
3647
3836
|
|
|
3648
3837
|
/** Draw directly to a 2d canvas context in world space
|
|
3649
3838
|
* @param {Vector2} pos
|
|
@@ -3651,7 +3840,7 @@ function drawCircle(pos, radius=1, color=new Color, lineWidth=0, lineColor=BLACK
|
|
|
3651
3840
|
* @param {number} angle
|
|
3652
3841
|
* @param {boolean} [mirror]
|
|
3653
3842
|
* @param {Function} [drawFunction]
|
|
3654
|
-
* @param {boolean}
|
|
3843
|
+
* @param {boolean} [screenSpace=false]
|
|
3655
3844
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
3656
3845
|
* @memberof Draw */
|
|
3657
3846
|
function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpace=false, context=drawContext)
|
|
@@ -3721,7 +3910,7 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
|
|
|
3721
3910
|
* @param {number} [maxWidth]
|
|
3722
3911
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
3723
3912
|
* @memberof Draw */
|
|
3724
|
-
function drawTextScreen(text, pos, size=1, color=
|
|
3913
|
+
function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, maxWidth, context=overlayContext)
|
|
3725
3914
|
{
|
|
3726
3915
|
context.fillStyle = color.toString();
|
|
3727
3916
|
context.strokeStyle = lineColor.toString();
|
|
@@ -3729,7 +3918,6 @@ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineCol
|
|
|
3729
3918
|
context.textAlign = textAlign;
|
|
3730
3919
|
context.font = size + 'px '+ font;
|
|
3731
3920
|
context.textBaseline = 'middle';
|
|
3732
|
-
context.lineJoin = 'round';
|
|
3733
3921
|
|
|
3734
3922
|
const lines = (text+'').split('\n');
|
|
3735
3923
|
let posY = pos.y;
|
|
@@ -3753,7 +3941,7 @@ function screenToWorld(screenPos)
|
|
|
3753
3941
|
{
|
|
3754
3942
|
let cameraPosRelativeX = (screenPos.x - mainCanvasSize.x/2 + .5) / cameraScale;
|
|
3755
3943
|
let cameraPosRelativeY = (screenPos.y - mainCanvasSize.y/2 + .5) / -cameraScale;
|
|
3756
|
-
if (cameraAngle)
|
|
3944
|
+
if (cameraAngle)
|
|
3757
3945
|
{
|
|
3758
3946
|
// apply camera rotation
|
|
3759
3947
|
const cos = Math.cos(-cameraAngle), sin = Math.sin(-cameraAngle);
|
|
@@ -3846,7 +4034,7 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
|
|
|
3846
4034
|
{
|
|
3847
4035
|
// white texture with no additive alpha, no need to tint
|
|
3848
4036
|
context.globalAlpha = color.a;
|
|
3849
|
-
context.drawImage(image, sx+sx2, sy+sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
|
|
4037
|
+
context.drawImage(image, sx+sx2, sy+sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
|
|
3850
4038
|
context.globalAlpha = 1;
|
|
3851
4039
|
}
|
|
3852
4040
|
else
|
|
@@ -3867,7 +4055,7 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
|
|
|
3867
4055
|
for (let i = 0; i < data.length; ++i)
|
|
3868
4056
|
data[i] = data[i] * colorMultiply[i&3] + colorAdd[i&3] |0;
|
|
3869
4057
|
workContext.putImageData(imageData, 0, 0);
|
|
3870
|
-
context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
|
|
4058
|
+
context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
|
|
3871
4059
|
}
|
|
3872
4060
|
else
|
|
3873
4061
|
{
|
|
@@ -3880,7 +4068,7 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
|
|
|
3880
4068
|
}
|
|
3881
4069
|
workContext.putImageData(imageData, 0, 0);
|
|
3882
4070
|
context.globalAlpha = color.a;
|
|
3883
|
-
context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
|
|
4071
|
+
context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
|
|
3884
4072
|
context.globalAlpha = 1;
|
|
3885
4073
|
}
|
|
3886
4074
|
}
|
|
@@ -3907,7 +4095,7 @@ function toggleFullscreen()
|
|
|
3907
4095
|
}
|
|
3908
4096
|
|
|
3909
4097
|
/** Set the cursor style
|
|
3910
|
-
* @param {string} cursorStyle - CSS cursor style (auto, none, crosshair, etc)
|
|
4098
|
+
* @param {string} [cursorStyle] - CSS cursor style (auto, none, crosshair, etc)
|
|
3911
4099
|
* @memberof Draw */
|
|
3912
4100
|
function setCursor(cursorStyle = 'auto')
|
|
3913
4101
|
{
|
|
@@ -3919,7 +4107,7 @@ function setCursor(cursorStyle = 'auto')
|
|
|
3919
4107
|
|
|
3920
4108
|
let engineFontImage;
|
|
3921
4109
|
|
|
3922
|
-
/**
|
|
4110
|
+
/**
|
|
3923
4111
|
* Font Image Object - Draw text on a 2D canvas by using characters in an image
|
|
3924
4112
|
* - 96 characters (from space to tilde) are stored in an image
|
|
3925
4113
|
* - Uses a default 8x8 font if none is supplied
|
|
@@ -3927,9 +4115,9 @@ let engineFontImage;
|
|
|
3927
4115
|
* @example
|
|
3928
4116
|
* // use built in font
|
|
3929
4117
|
* const font = new FontImage;
|
|
3930
|
-
*
|
|
4118
|
+
*
|
|
3931
4119
|
* // draw text
|
|
3932
|
-
* font.drawTextScreen(
|
|
4120
|
+
* font.drawTextScreen('LittleJS\nHello World!', vec2(200, 50));
|
|
3933
4121
|
*/
|
|
3934
4122
|
class FontImage
|
|
3935
4123
|
{
|
|
@@ -3937,7 +4125,6 @@ class FontImage
|
|
|
3937
4125
|
* @param {HTMLImageElement} [image] - Image for the font, if undefined default font is used
|
|
3938
4126
|
* @param {Vector2} [tileSize=(8,8)] - Size of the font source tiles
|
|
3939
4127
|
* @param {Vector2} [paddingSize=(0,1)] - How much extra space to add between characters
|
|
3940
|
-
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext] - context to draw to
|
|
3941
4128
|
*/
|
|
3942
4129
|
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), context=overlayContext)
|
|
3943
4130
|
{
|
|
@@ -3951,7 +4138,6 @@ class FontImage
|
|
|
3951
4138
|
this.image = image || engineFontImage;
|
|
3952
4139
|
this.tileSize = tileSize;
|
|
3953
4140
|
this.paddingSize = paddingSize;
|
|
3954
|
-
this.context = context;
|
|
3955
4141
|
}
|
|
3956
4142
|
|
|
3957
4143
|
/** Draw text in world space using the image font
|
|
@@ -3959,23 +4145,32 @@ class FontImage
|
|
|
3959
4145
|
* @param {Vector2} pos
|
|
3960
4146
|
* @param {number} [scale=.25]
|
|
3961
4147
|
* @param {boolean} [center]
|
|
4148
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D}[context=drawContext]
|
|
3962
4149
|
*/
|
|
3963
|
-
drawText(text, pos, scale=1, center)
|
|
4150
|
+
drawText(text, pos, scale=1, center, context=drawContext)
|
|
3964
4151
|
{
|
|
3965
|
-
this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center);
|
|
4152
|
+
this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center, context);
|
|
3966
4153
|
}
|
|
3967
4154
|
|
|
3968
|
-
/** Draw text in
|
|
4155
|
+
/** Draw text on overlay canvas in world space using the image font
|
|
4156
|
+
* @param {string} text
|
|
4157
|
+
* @param {Vector2} pos
|
|
4158
|
+
* @param {number} [scale]
|
|
4159
|
+
* @param {boolean} [center]
|
|
4160
|
+
*/
|
|
4161
|
+
drawTextOverlay(text, pos, scale=4, center)
|
|
4162
|
+
{ this.drawText(text, pos, scale, center, overlayContext); }
|
|
4163
|
+
|
|
4164
|
+
/** Draw text on overlay canvas in screen space using the image font
|
|
3969
4165
|
* @param {string} text
|
|
3970
4166
|
* @param {Vector2} pos
|
|
3971
4167
|
* @param {number} [scale]
|
|
3972
4168
|
* @param {boolean} [center]
|
|
4169
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
3973
4170
|
*/
|
|
3974
|
-
drawTextScreen(text, pos, scale=4, center)
|
|
4171
|
+
drawTextScreen(text, pos, scale=4, center, context=overlayContext)
|
|
3975
4172
|
{
|
|
3976
|
-
const context = this.context;
|
|
3977
4173
|
context.save();
|
|
3978
|
-
|
|
3979
4174
|
const size = this.tileSize;
|
|
3980
4175
|
const drawSize = size.add(this.paddingSize).scale(scale);
|
|
3981
4176
|
const cols = this.image.width / this.tileSize.x |0;
|
|
@@ -3994,15 +4189,14 @@ class FontImage
|
|
|
3994
4189
|
const x = tile % cols;
|
|
3995
4190
|
const y = tile / cols |0;
|
|
3996
4191
|
const drawPos = pos.add(vec2(j,i).multiply(drawSize));
|
|
3997
|
-
context.drawImage(this.image, x * size.x, y * size.y, size.x, size.y,
|
|
4192
|
+
context.drawImage(this.image, x * size.x, y * size.y, size.x, size.y,
|
|
3998
4193
|
drawPos.x - centerOffset, drawPos.y, size.x * scale, size.y * scale);
|
|
3999
4194
|
}
|
|
4000
4195
|
});
|
|
4001
|
-
|
|
4002
4196
|
context.restore();
|
|
4003
4197
|
}
|
|
4004
4198
|
}
|
|
4005
|
-
/**
|
|
4199
|
+
/**
|
|
4006
4200
|
* LittleJS Input System
|
|
4007
4201
|
* - Tracks keyboard down, pressed, and released
|
|
4008
4202
|
* - Tracks mouse buttons, position, and wheel
|
|
@@ -4018,10 +4212,10 @@ class FontImage
|
|
|
4018
4212
|
* @return {boolean}
|
|
4019
4213
|
* @memberof Input */
|
|
4020
4214
|
function keyIsDown(key, device=0)
|
|
4021
|
-
{
|
|
4215
|
+
{
|
|
4022
4216
|
ASSERT(key !== undefined, 'key is undefined');
|
|
4023
|
-
ASSERT(device > 0 || typeof key
|
|
4024
|
-
return inputData[device] && !!(inputData[device][key] & 1);
|
|
4217
|
+
ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
|
|
4218
|
+
return inputData[device] && !!(inputData[device][key] & 1);
|
|
4025
4219
|
}
|
|
4026
4220
|
|
|
4027
4221
|
/** Returns true if device key was pressed this frame
|
|
@@ -4030,10 +4224,10 @@ function keyIsDown(key, device=0)
|
|
|
4030
4224
|
* @return {boolean}
|
|
4031
4225
|
* @memberof Input */
|
|
4032
4226
|
function keyWasPressed(key, device=0)
|
|
4033
|
-
{
|
|
4227
|
+
{
|
|
4034
4228
|
ASSERT(key !== undefined, 'key is undefined');
|
|
4035
|
-
ASSERT(device > 0 || typeof key
|
|
4036
|
-
return inputData[device] && !!(inputData[device][key] & 2);
|
|
4229
|
+
ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
|
|
4230
|
+
return inputData[device] && !!(inputData[device][key] & 2);
|
|
4037
4231
|
}
|
|
4038
4232
|
|
|
4039
4233
|
/** Returns true if device key was released this frame
|
|
@@ -4042,9 +4236,9 @@ function keyWasPressed(key, device=0)
|
|
|
4042
4236
|
* @return {boolean}
|
|
4043
4237
|
* @memberof Input */
|
|
4044
4238
|
function keyWasReleased(key, device=0)
|
|
4045
|
-
{
|
|
4239
|
+
{
|
|
4046
4240
|
ASSERT(key !== undefined, 'key is undefined');
|
|
4047
|
-
ASSERT(device > 0 || typeof key
|
|
4241
|
+
ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
|
|
4048
4242
|
return inputData[device] && !!(inputData[device][key] & 4);
|
|
4049
4243
|
}
|
|
4050
4244
|
|
|
@@ -4166,7 +4360,7 @@ function gamepadWasReleased(button, gamepad=0)
|
|
|
4166
4360
|
* @param {number} [gamepad]
|
|
4167
4361
|
* @return {Vector2}
|
|
4168
4362
|
* @memberof Input */
|
|
4169
|
-
function gamepadStick(stick,
|
|
4363
|
+
function gamepadStick(stick, gamepad=0)
|
|
4170
4364
|
{ return gamepadStickData[gamepad] ? gamepadStickData[gamepad][stick] || vec2() : vec2(); }
|
|
4171
4365
|
|
|
4172
4366
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -4243,10 +4437,10 @@ function inputInit()
|
|
|
4243
4437
|
{
|
|
4244
4438
|
// handle remapping wasd keys to directions
|
|
4245
4439
|
return inputWASDEmulateDirection ?
|
|
4246
|
-
c
|
|
4247
|
-
c
|
|
4248
|
-
c
|
|
4249
|
-
c
|
|
4440
|
+
c === 'KeyW' ? 'ArrowUp' :
|
|
4441
|
+
c === 'KeyS' ? 'ArrowDown' :
|
|
4442
|
+
c === 'KeyA' ? 'ArrowLeft' :
|
|
4443
|
+
c === 'KeyD' ? 'ArrowRight' : c : c;
|
|
4250
4444
|
}
|
|
4251
4445
|
function onMouseDown(e)
|
|
4252
4446
|
{
|
|
@@ -4254,9 +4448,9 @@ function inputInit()
|
|
|
4254
4448
|
return;
|
|
4255
4449
|
|
|
4256
4450
|
// fix stalled audio requiring user interaction
|
|
4257
|
-
if (soundEnable && !headlessMode && audioContext &&
|
|
4451
|
+
if (soundEnable && !headlessMode && audioContext && !audioIsRunning())
|
|
4258
4452
|
audioContext.resume();
|
|
4259
|
-
|
|
4453
|
+
|
|
4260
4454
|
isUsingGamepad = false;
|
|
4261
4455
|
inputData[0][e.button] = 3;
|
|
4262
4456
|
mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
|
|
@@ -4299,8 +4493,8 @@ function gamepadsUpdate()
|
|
|
4299
4493
|
const applyDeadZones = (v)=>
|
|
4300
4494
|
{
|
|
4301
4495
|
const min=.3, max=.8;
|
|
4302
|
-
const deadZone = (v)=>
|
|
4303
|
-
v >
|
|
4496
|
+
const deadZone = (v)=>
|
|
4497
|
+
v > min ? percent(v, min, max) :
|
|
4304
4498
|
v < -min ? -percent(-v, min, max) : 0;
|
|
4305
4499
|
return vec2(deadZone(v.x), deadZone(-v.y)).clampLength();
|
|
4306
4500
|
}
|
|
@@ -4308,30 +4502,29 @@ function gamepadsUpdate()
|
|
|
4308
4502
|
// update touch gamepad if enabled
|
|
4309
4503
|
if (touchGamepadEnable && isTouchDevice)
|
|
4310
4504
|
{
|
|
4311
|
-
|
|
4312
|
-
|
|
4505
|
+
if (!touchGamepadTimer.isSet())
|
|
4506
|
+
return;
|
|
4507
|
+
|
|
4508
|
+
// read virtual analog stick
|
|
4509
|
+
const sticks = gamepadStickData[0] || (gamepadStickData[0] = []);
|
|
4510
|
+
sticks[0] = vec2();
|
|
4511
|
+
if (touchGamepadAnalog)
|
|
4512
|
+
sticks[0] = applyDeadZones(touchGamepadStick);
|
|
4513
|
+
else if (touchGamepadStick.lengthSquared() > .3)
|
|
4313
4514
|
{
|
|
4314
|
-
//
|
|
4315
|
-
|
|
4316
|
-
sticks[0] =
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
else if (touchGamepadStick.lengthSquared() > .3)
|
|
4320
|
-
{
|
|
4321
|
-
// convert to 8 way dpad
|
|
4322
|
-
sticks[0].x = Math.round(touchGamepadStick.x);
|
|
4323
|
-
sticks[0].y = -Math.round(touchGamepadStick.y);
|
|
4324
|
-
sticks[0] = sticks[0].clampLength();
|
|
4325
|
-
}
|
|
4515
|
+
// convert to 8 way dpad
|
|
4516
|
+
sticks[0].x = Math.round(touchGamepadStick.x);
|
|
4517
|
+
sticks[0].y = -Math.round(touchGamepadStick.y);
|
|
4518
|
+
sticks[0] = sticks[0].clampLength();
|
|
4519
|
+
}
|
|
4326
4520
|
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
}
|
|
4521
|
+
// read virtual gamepad buttons
|
|
4522
|
+
const data = inputData[1] || (inputData[1] = []);
|
|
4523
|
+
for (let i=10; i--;)
|
|
4524
|
+
{
|
|
4525
|
+
const j = i === 3 ? 2 : i === 2 ? 3 : i; // fix button locations
|
|
4526
|
+
const wasDown = gamepadIsDown(j,0);
|
|
4527
|
+
data[j] = touchGamepadButtons[i] ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
|
|
4335
4528
|
}
|
|
4336
4529
|
}
|
|
4337
4530
|
|
|
@@ -4357,7 +4550,7 @@ function gamepadsUpdate()
|
|
|
4357
4550
|
// read analog sticks
|
|
4358
4551
|
for (let j = 0; j < gamepad.axes.length-1; j+=2)
|
|
4359
4552
|
sticks[j>>1] = applyDeadZones(vec2(gamepad.axes[j],gamepad.axes[j+1]));
|
|
4360
|
-
|
|
4553
|
+
|
|
4361
4554
|
// read buttons
|
|
4362
4555
|
for (let j = gamepad.buttons.length; j--;)
|
|
4363
4556
|
{
|
|
@@ -4373,14 +4566,14 @@ function gamepadsUpdate()
|
|
|
4373
4566
|
{
|
|
4374
4567
|
// copy dpad to left analog stick when pressed
|
|
4375
4568
|
const dpad = vec2(
|
|
4376
|
-
(gamepadIsDown(15,i)&&1) - (gamepadIsDown(14,i)&&1),
|
|
4569
|
+
(gamepadIsDown(15,i)&&1) - (gamepadIsDown(14,i)&&1),
|
|
4377
4570
|
(gamepadIsDown(12,i)&&1) - (gamepadIsDown(13,i)&&1));
|
|
4378
4571
|
if (dpad.lengthSquared())
|
|
4379
4572
|
sticks[0] = dpad.clampLength();
|
|
4380
4573
|
}
|
|
4381
4574
|
|
|
4382
4575
|
// disable touch gamepad if using real gamepad
|
|
4383
|
-
touchGamepadEnable && isUsingGamepad && touchGamepadTimer.unset();
|
|
4576
|
+
touchGamepadEnable && isUsingGamepad && touchGamepadTimer.unset();
|
|
4384
4577
|
}
|
|
4385
4578
|
}
|
|
4386
4579
|
}
|
|
@@ -4405,20 +4598,13 @@ function vibrateStop() { vibrate(0); }
|
|
|
4405
4598
|
const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
4406
4599
|
|
|
4407
4600
|
// touch gamepad internal variables
|
|
4408
|
-
let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
|
|
4601
|
+
let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick = vec2();
|
|
4409
4602
|
|
|
4410
4603
|
// enable touch input mouse passthrough
|
|
4411
4604
|
function touchInputInit()
|
|
4412
4605
|
{
|
|
4413
4606
|
// add non passive touch event listeners
|
|
4414
4607
|
let handleTouch = handleTouchDefault;
|
|
4415
|
-
if (touchGamepadEnable)
|
|
4416
|
-
{
|
|
4417
|
-
// touch input internal variables
|
|
4418
|
-
handleTouch = handleTouchGamepad;
|
|
4419
|
-
touchGamepadButtons = [];
|
|
4420
|
-
touchGamepadStick = vec2();
|
|
4421
|
-
}
|
|
4422
4608
|
document.addEventListener('touchstart', (e) => handleTouch(e), { passive: false });
|
|
4423
4609
|
document.addEventListener('touchmove', (e) => handleTouch(e), { passive: false });
|
|
4424
4610
|
document.addEventListener('touchend', (e) => handleTouch(e), { passive: false });
|
|
@@ -4427,8 +4613,15 @@ function touchInputInit()
|
|
|
4427
4613
|
let wasTouching;
|
|
4428
4614
|
function handleTouchDefault(e)
|
|
4429
4615
|
{
|
|
4616
|
+
if (!touchInputEnable)
|
|
4617
|
+
return;
|
|
4618
|
+
|
|
4619
|
+
// route touch to gamepad
|
|
4620
|
+
if (touchGamepadEnable)
|
|
4621
|
+
handleTouchGamepad(e);
|
|
4622
|
+
|
|
4430
4623
|
// fix stalled audio requiring user interaction
|
|
4431
|
-
if (soundEnable && !headlessMode && audioContext &&
|
|
4624
|
+
if (soundEnable && !headlessMode && audioContext && !audioIsRunning())
|
|
4432
4625
|
audioContext.resume();
|
|
4433
4626
|
|
|
4434
4627
|
// check if touching and pass to mouse events
|
|
@@ -4457,7 +4650,7 @@ function touchInputInit()
|
|
|
4457
4650
|
// prevent default handling like copy and magnifier lens
|
|
4458
4651
|
if (inputPreventDefault && document.hasFocus()) // allow document to get focus
|
|
4459
4652
|
e.preventDefault();
|
|
4460
|
-
|
|
4653
|
+
|
|
4461
4654
|
// must return true so the document will get focus
|
|
4462
4655
|
return true;
|
|
4463
4656
|
}
|
|
@@ -4469,7 +4662,7 @@ function touchInputInit()
|
|
|
4469
4662
|
touchGamepadStick = vec2();
|
|
4470
4663
|
touchGamepadButtons = [];
|
|
4471
4664
|
isUsingGamepad = true;
|
|
4472
|
-
|
|
4665
|
+
|
|
4473
4666
|
const touching = e.touches.length;
|
|
4474
4667
|
if (touching)
|
|
4475
4668
|
{
|
|
@@ -4478,9 +4671,6 @@ function touchInputInit()
|
|
|
4478
4671
|
{
|
|
4479
4672
|
// touch anywhere to press start when paused
|
|
4480
4673
|
touchGamepadButtons[9] = 1;
|
|
4481
|
-
|
|
4482
|
-
// call default touch handler so normal touch events still work
|
|
4483
|
-
handleTouchDefault(e);
|
|
4484
4674
|
return;
|
|
4485
4675
|
}
|
|
4486
4676
|
}
|
|
@@ -4511,12 +4701,6 @@ function touchInputInit()
|
|
|
4511
4701
|
touchGamepadButtons[9] = 1;
|
|
4512
4702
|
}
|
|
4513
4703
|
}
|
|
4514
|
-
|
|
4515
|
-
// call default touch handler so normal touch events still work
|
|
4516
|
-
handleTouchDefault(e);
|
|
4517
|
-
|
|
4518
|
-
// must return true so the document will get focus
|
|
4519
|
-
return true;
|
|
4520
4704
|
}
|
|
4521
4705
|
}
|
|
4522
4706
|
|
|
@@ -4526,7 +4710,7 @@ function touchGamepadRender()
|
|
|
4526
4710
|
if (!touchInputEnable || !isTouchDevice || headlessMode) return;
|
|
4527
4711
|
if (!touchGamepadEnable || !touchGamepadTimer.isSet())
|
|
4528
4712
|
return;
|
|
4529
|
-
|
|
4713
|
+
|
|
4530
4714
|
// fade off when not touching or paused
|
|
4531
4715
|
const alpha = percent(touchGamepadTimer.get(), 4, 3);
|
|
4532
4716
|
if (!alpha || paused)
|
|
@@ -4557,11 +4741,11 @@ function touchGamepadRender()
|
|
|
4557
4741
|
const angle = i*PI/4;
|
|
4558
4742
|
context.arc(leftCenter.x, leftCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
|
|
4559
4743
|
i%2 && context.arc(leftCenter.x, leftCenter.y, touchGamepadSize*.33, angle, angle);
|
|
4560
|
-
i
|
|
4744
|
+
i===1 && context.fill();
|
|
4561
4745
|
}
|
|
4562
4746
|
context.stroke();
|
|
4563
4747
|
}
|
|
4564
|
-
|
|
4748
|
+
|
|
4565
4749
|
// draw right face buttons
|
|
4566
4750
|
const rightCenter = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
4567
4751
|
for (let i=4; i--;)
|
|
@@ -4596,8 +4780,8 @@ function pointerLockExit() { document.exitPointerLock && document.exitPointerLoc
|
|
|
4596
4780
|
/** Check if pointer is locked (true if locked)
|
|
4597
4781
|
* @return {boolean}
|
|
4598
4782
|
* @memberof Input */
|
|
4599
|
-
function pointerLockIsActive() { return document.pointerLockElement
|
|
4600
|
-
/**
|
|
4783
|
+
function pointerLockIsActive() { return document.pointerLockElement === mainCanvas; }
|
|
4784
|
+
/**
|
|
4601
4785
|
* LittleJS Audio System
|
|
4602
4786
|
* - <a href=https://killedbyapixel.github.io/ZzFX/>ZzFX Sound Effects</a> - ZzFX Sound Effect Generator
|
|
4603
4787
|
* - <a href=https://keithclark.github.io/ZzFXM/>ZzFXM Music</a> - ZzFXM Music System
|
|
@@ -4618,10 +4802,21 @@ let audioContext = new AudioContext;
|
|
|
4618
4802
|
* @memberof Audio */
|
|
4619
4803
|
let audioMasterGain;
|
|
4620
4804
|
|
|
4805
|
+
/** Default sample rate used for sounds
|
|
4806
|
+
* @default 44100
|
|
4807
|
+
* @memberof Audio */
|
|
4808
|
+
const audioDefaultSampleRate = 44100;
|
|
4809
|
+
|
|
4810
|
+
/** Check if the audio context is running and available for playback
|
|
4811
|
+
* @return {boolean} - True if the audio context is running
|
|
4812
|
+
* @memberof Audio */
|
|
4813
|
+
function audioIsRunning()
|
|
4814
|
+
{ return audioContext.state === 'running'; }
|
|
4815
|
+
|
|
4621
4816
|
function audioInit()
|
|
4622
4817
|
{
|
|
4623
4818
|
if (!soundEnable || headlessMode) return;
|
|
4624
|
-
|
|
4819
|
+
|
|
4625
4820
|
audioMasterGain = audioContext.createGain();
|
|
4626
4821
|
audioMasterGain.connect(audioContext.destination);
|
|
4627
4822
|
audioMasterGain.gain.value = soundVolume; // set starting value
|
|
@@ -4629,14 +4824,14 @@ function audioInit()
|
|
|
4629
4824
|
|
|
4630
4825
|
///////////////////////////////////////////////////////////////////////////////
|
|
4631
4826
|
|
|
4632
|
-
/**
|
|
4827
|
+
/**
|
|
4633
4828
|
* Sound Object - Stores a sound for later use and can be played positionally
|
|
4634
|
-
*
|
|
4829
|
+
*
|
|
4635
4830
|
* <a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a>
|
|
4636
4831
|
* @example
|
|
4637
4832
|
* // create a sound
|
|
4638
4833
|
* const sound_example = new Sound([.5,.5]);
|
|
4639
|
-
*
|
|
4834
|
+
*
|
|
4640
4835
|
* // play the sound
|
|
4641
4836
|
* sound_example.play();
|
|
4642
4837
|
*/
|
|
@@ -4653,33 +4848,41 @@ class Sound
|
|
|
4653
4848
|
|
|
4654
4849
|
/** @property {number} - World space max range of sound */
|
|
4655
4850
|
this.range = range;
|
|
4656
|
-
|
|
4657
4851
|
/** @property {number} - At what percentage of range should it start tapering */
|
|
4658
4852
|
this.taper = taper;
|
|
4659
|
-
|
|
4660
4853
|
/** @property {number} - How much to randomize frequency each time sound plays */
|
|
4661
4854
|
this.randomness = 0;
|
|
4855
|
+
/** @property {number} - Sample rate for this sound */
|
|
4856
|
+
this.sampleRate = audioDefaultSampleRate;
|
|
4857
|
+
/** @property {number} - Percentage of this sound currently loaded */
|
|
4858
|
+
this.loadedPercent = 0;
|
|
4662
4859
|
|
|
4860
|
+
// generate zzfx sound now for fast playback
|
|
4663
4861
|
if (zzfxSound)
|
|
4664
4862
|
{
|
|
4665
|
-
//
|
|
4666
|
-
const defaultRandomness = .05;
|
|
4667
|
-
this.randomness = zzfxSound[
|
|
4668
|
-
|
|
4863
|
+
// remove randomness so it can be applied on playback
|
|
4864
|
+
const randomnessIndex = 1, defaultRandomness = .05;
|
|
4865
|
+
this.randomness = zzfxSound[randomnessIndex] !== undefined ?
|
|
4866
|
+
zzfxSound[randomnessIndex] : defaultRandomness;
|
|
4867
|
+
zzfxSound[randomnessIndex] = 0;
|
|
4868
|
+
|
|
4869
|
+
// generate the zzfx samples
|
|
4669
4870
|
this.sampleChannels = [zzfxG(...zzfxSound)];
|
|
4670
|
-
this.
|
|
4871
|
+
this.loadedPercent = 1;
|
|
4671
4872
|
}
|
|
4672
4873
|
}
|
|
4673
4874
|
|
|
4674
4875
|
/** Play the sound
|
|
4675
|
-
*
|
|
4676
|
-
* @param {
|
|
4677
|
-
* @param {number} [
|
|
4678
|
-
* @param {number} [
|
|
4679
|
-
* @param {
|
|
4680
|
-
* @
|
|
4876
|
+
* Sounds may not play until a user interaction occurs
|
|
4877
|
+
* @param {Vector2} [pos] - World space position to play the sound if any
|
|
4878
|
+
* @param {number} [volume] - How much to scale volume by
|
|
4879
|
+
* @param {number} [pitch] - How much to scale pitch by
|
|
4880
|
+
* @param {number} [randomnessScale] - How much to scale pitch randomness
|
|
4881
|
+
* @param {boolean} [loop] - Should the sound loop?
|
|
4882
|
+
* @param {boolean} [paused] - Should the sound start paused
|
|
4883
|
+
* @return {SoundInstance} - The audio source node
|
|
4681
4884
|
*/
|
|
4682
|
-
play(pos, volume=1, pitch=1, randomnessScale=1, loop=false)
|
|
4885
|
+
play(pos, volume=1, pitch=1, randomnessScale=1, loop=false, paused=false)
|
|
4683
4886
|
{
|
|
4684
4887
|
if (!soundEnable || headlessMode) return;
|
|
4685
4888
|
if (!this.sampleChannels) return;
|
|
@@ -4702,75 +4905,55 @@ class Sound
|
|
|
4702
4905
|
// get pan from screen space coords
|
|
4703
4906
|
pan = worldToScreen(pos).x * 2/mainCanvas.width - 1;
|
|
4704
4907
|
}
|
|
4705
|
-
|
|
4706
|
-
// play the sound
|
|
4707
|
-
const playbackRate = pitch + pitch * this.randomness*randomnessScale*rand(-1,1);
|
|
4708
|
-
this.gainNode = audioContext.createGain();
|
|
4709
|
-
this.source = playSamples(this.sampleChannels, volume, playbackRate, pan, loop, this.sampleRate, this.gainNode);
|
|
4710
|
-
return this.source;
|
|
4711
|
-
}
|
|
4712
|
-
|
|
4713
|
-
/** Set the sound volume of the most recently played instance of this sound
|
|
4714
|
-
* @param {number} [volume] - How much to scale volume by
|
|
4715
|
-
*/
|
|
4716
|
-
setVolume(volume=1)
|
|
4717
|
-
{
|
|
4718
|
-
if (this.gainNode)
|
|
4719
|
-
this.gainNode.gain.value = volume;
|
|
4720
|
-
}
|
|
4721
|
-
|
|
4722
|
-
/** Stop the last instance of this sound that was played
|
|
4723
|
-
* @param {number} [fadeTime] - How long to fade out (seconds)
|
|
4724
|
-
*/
|
|
4725
|
-
stop(fadeTime=0)
|
|
4726
|
-
{
|
|
4727
|
-
if (!this.source)
|
|
4728
|
-
return;
|
|
4729
4908
|
|
|
4730
|
-
//
|
|
4731
|
-
const
|
|
4732
|
-
|
|
4733
|
-
this.gainNode.gain.linearRampToValueAtTime(1, startFade);
|
|
4734
|
-
this.gainNode.gain.linearRampToValueAtTime(0, endFade);
|
|
4735
|
-
this.source.stop(endFade);
|
|
4736
|
-
this.source = undefined;
|
|
4909
|
+
// Create and return sound instance
|
|
4910
|
+
const rate = pitch + pitch * this.randomness*randomnessScale*rand(-1,1);
|
|
4911
|
+
return new SoundInstance(this, volume, rate, pan, loop, paused);
|
|
4737
4912
|
}
|
|
4738
4913
|
|
|
4739
|
-
/**
|
|
4740
|
-
* @
|
|
4914
|
+
/** Play a music track that loops by default
|
|
4915
|
+
* @param {number} [volume] - Volume to play the music at
|
|
4916
|
+
* @param {boolean} [loop] - Should the music loop?
|
|
4917
|
+
* @param {boolean} [paused] - Should the music start paused
|
|
4918
|
+
* @return {SoundInstance} - The audio source node
|
|
4741
4919
|
*/
|
|
4742
|
-
|
|
4920
|
+
playMusic(volume=1, loop=true, paused=false)
|
|
4921
|
+
{ return this.play(undefined, volume, 1, 0, loop, paused); }
|
|
4743
4922
|
|
|
4744
|
-
/** Play the sound as a note with a semitone offset
|
|
4923
|
+
/** Play the sound as a musical note with a semitone offset
|
|
4924
|
+
* This can be used to play music with chromatic scales
|
|
4745
4925
|
* @param {number} semitoneOffset - How many semitones to offset pitch
|
|
4746
|
-
* @param {Vector2} [pos] - World space position to play the sound
|
|
4747
|
-
* @param {number} [volume=1] - How much to scale volume by
|
|
4748
|
-
* @return {
|
|
4926
|
+
* @param {Vector2} [pos] - World space position to play the sound if any
|
|
4927
|
+
* @param {number} [volume=1] - How much to scale volume by
|
|
4928
|
+
* @return {SoundInstance} - The audio source node
|
|
4749
4929
|
*/
|
|
4750
4930
|
playNote(semitoneOffset, pos, volume)
|
|
4751
|
-
{
|
|
4931
|
+
{
|
|
4932
|
+
const pitch = getNoteFrequency(semitoneOffset, 1);
|
|
4933
|
+
return this.play(pos, volume, pitch, 0);
|
|
4934
|
+
}
|
|
4752
4935
|
|
|
4753
4936
|
/** Get how long this sound is in seconds
|
|
4754
4937
|
* @return {number} - How long the sound is in seconds (undefined if loading)
|
|
4755
4938
|
*/
|
|
4756
|
-
getDuration()
|
|
4939
|
+
getDuration()
|
|
4757
4940
|
{ return this.sampleChannels && this.sampleChannels[0].length / this.sampleRate; }
|
|
4758
|
-
|
|
4759
|
-
/** Check if sound is
|
|
4760
|
-
* @return {boolean} - True if sound is
|
|
4941
|
+
|
|
4942
|
+
/** Check if sound is loaded, for sounds fetched from a url
|
|
4943
|
+
* @return {boolean} - True if sound is loaded and ready to play
|
|
4761
4944
|
*/
|
|
4762
|
-
|
|
4945
|
+
isLoaded() { return this.loadedPercent === 1; }
|
|
4763
4946
|
}
|
|
4764
4947
|
|
|
4765
4948
|
///////////////////////////////////////////////////////////////////////////////
|
|
4766
4949
|
|
|
4767
|
-
/**
|
|
4950
|
+
/**
|
|
4768
4951
|
* Sound Wave Object - Stores a wave sound for later use and can be played positionally
|
|
4769
4952
|
* - this can be used to play wave, mp3, and ogg files
|
|
4770
4953
|
* @example
|
|
4771
4954
|
* // create a sound
|
|
4772
4955
|
* const sound_example = new SoundWave('sound.mp3');
|
|
4773
|
-
*
|
|
4956
|
+
*
|
|
4774
4957
|
* // play the sound
|
|
4775
4958
|
* sound_example.play();
|
|
4776
4959
|
*/
|
|
@@ -4799,29 +4982,205 @@ class SoundWave extends Sound
|
|
|
4799
4982
|
* @return {Promise<void>} */
|
|
4800
4983
|
async loadSound(filename)
|
|
4801
4984
|
{
|
|
4802
|
-
const response = await fetch(filename);
|
|
4803
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
4804
|
-
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4985
|
+
const response = await fetch(filename);
|
|
4986
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
4987
|
+
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
|
4988
|
+
|
|
4989
|
+
// convert audio buffer to sample channels across multiple frames
|
|
4990
|
+
const channelCount = audioBuffer.numberOfChannels;
|
|
4991
|
+
const samplesPerFrame = 1e5;
|
|
4992
|
+
const sampleChannels = [];
|
|
4993
|
+
for (let channel = 0; channel < channelCount; channel++)
|
|
4994
|
+
{
|
|
4995
|
+
const channelData = audioBuffer.getChannelData(channel);
|
|
4996
|
+
const channelLength = channelData.length;
|
|
4997
|
+
sampleChannels[channel] = new Array(channelLength);
|
|
4998
|
+
let sampleIndex = 0;
|
|
4999
|
+
while (sampleIndex < channelLength)
|
|
5000
|
+
{
|
|
5001
|
+
// yield to next frame
|
|
5002
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
5003
|
+
|
|
5004
|
+
// copy chunk of samples
|
|
5005
|
+
const endIndex = min(sampleIndex + samplesPerFrame, channelLength);
|
|
5006
|
+
for (; sampleIndex < endIndex; sampleIndex++)
|
|
5007
|
+
sampleChannels[channel][sampleIndex] = channelData[sampleIndex];
|
|
5008
|
+
|
|
5009
|
+
// update loaded percent
|
|
5010
|
+
const samplesTotal = channelCount * channelLength;
|
|
5011
|
+
const samplesProcessed = channel * channelLength + sampleIndex;
|
|
5012
|
+
this.loadedPercent = samplesProcessed / samplesTotal;
|
|
5013
|
+
}
|
|
5014
|
+
}
|
|
5015
|
+
|
|
5016
|
+
// setup the sound to be played
|
|
5017
|
+
this.sampleRate = audioBuffer.sampleRate;
|
|
5018
|
+
this.sampleChannels = sampleChannels;
|
|
5019
|
+
this.loadedPercent = 1;
|
|
5020
|
+
if (this.onloadCallback)
|
|
5021
|
+
this.onloadCallback();
|
|
5022
|
+
}
|
|
5023
|
+
}
|
|
5024
|
+
|
|
5025
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5026
|
+
|
|
5027
|
+
/**
|
|
5028
|
+
* Sound Instance - Wraps an AudioBufferSourceNode for individual sound control
|
|
5029
|
+
* Represents a single playing instance of a sound with pause/resume capabilities
|
|
5030
|
+
* @example
|
|
5031
|
+
* // Play a sound and get an instance for control
|
|
5032
|
+
* const jumpSound = new Sound([.5,.5,220]);
|
|
5033
|
+
* const instance = jumpSound.play();
|
|
5034
|
+
*
|
|
5035
|
+
* // Control the individual instance
|
|
5036
|
+
* instance.setVolume(.5);
|
|
5037
|
+
* instance.pause();
|
|
5038
|
+
* instance.unpause();
|
|
5039
|
+
* instance.stop();
|
|
5040
|
+
*/
|
|
5041
|
+
class SoundInstance
|
|
5042
|
+
{
|
|
5043
|
+
/** Create a sound instance
|
|
5044
|
+
* @param {Sound} sound - The sound object
|
|
5045
|
+
* @param {number} [volume] - How much to scale volume by
|
|
5046
|
+
* @param {number} [rate] - The playback rate to use
|
|
5047
|
+
* @param {number} [pan] - How much to apply stereo panning
|
|
5048
|
+
* @param {boolean} [loop] - Should the sound loop?
|
|
5049
|
+
* @param {boolean} [paused] - Should the sound start paused? */
|
|
5050
|
+
constructor(sound, volume=1, rate=1, pan=0, loop=false, paused=false)
|
|
5051
|
+
{
|
|
5052
|
+
ASSERT(sound instanceof Sound, 'SoundInstance requires a valid Sound object');
|
|
5053
|
+
ASSERT(volume >= 0, 'Sound volume must be positive or zero');
|
|
5054
|
+
ASSERT(rate >= 0, 'Sound rate must be positive or zero');
|
|
5055
|
+
ASSERT(isNumber(pan), 'Sound pan must be a number');
|
|
5056
|
+
|
|
5057
|
+
/** @property {Sound} - The sound object */
|
|
5058
|
+
this.sound = sound;
|
|
5059
|
+
/** @property {number} - How much to scale volume by */
|
|
5060
|
+
this.volume = volume;
|
|
5061
|
+
/** @property {number} - The playback rate to use */
|
|
5062
|
+
this.rate = rate;
|
|
5063
|
+
/** @property {number} - How much to apply stereo panning */
|
|
5064
|
+
this.pan = pan;
|
|
5065
|
+
/** @property {boolean} - Should the sound loop */
|
|
5066
|
+
this.loop = loop;
|
|
5067
|
+
/** @property {number} - Timestamp for audio context when paused */
|
|
5068
|
+
this.pausedTime = 0;
|
|
5069
|
+
/** @property {number} - Timestamp for audio context when started */
|
|
5070
|
+
this.startTime = undefined;
|
|
5071
|
+
/** @property {GainNode} - Gain node for the sound */
|
|
5072
|
+
this.gainNode = undefined;
|
|
5073
|
+
/** @property {AudioBufferSourceNode} - Source node of the audio */
|
|
5074
|
+
this.source = undefined;
|
|
5075
|
+
// setup end callback and start sound
|
|
5076
|
+
this.onendedCallback = (source)=>
|
|
5077
|
+
{
|
|
5078
|
+
if (source === this.source)
|
|
5079
|
+
this.source = undefined;
|
|
5080
|
+
};
|
|
5081
|
+
if (!paused)
|
|
5082
|
+
this.start();
|
|
5083
|
+
}
|
|
5084
|
+
|
|
5085
|
+
/** Start playing the sound instance from the offset time
|
|
5086
|
+
* @param {number} [offset] - Offset in seconds to start playback from
|
|
5087
|
+
*/
|
|
5088
|
+
start(offset=0)
|
|
5089
|
+
{
|
|
5090
|
+
ASSERT(offset >= 0, 'Sound start offset must be positive or zero');
|
|
5091
|
+
if (this.isPlaying())
|
|
5092
|
+
this.stop();
|
|
5093
|
+
this.gainNode = audioContext.createGain();
|
|
5094
|
+
this.source = playSamples(this.sound.sampleChannels, this.volume, this.rate, this.pan, this.loop, this.sound.sampleRate, this.gainNode, offset, this.onendedCallback);
|
|
5095
|
+
this.startTime = audioContext.currentTime - offset;
|
|
5096
|
+
this.pausedTime = undefined;
|
|
5097
|
+
}
|
|
5098
|
+
|
|
5099
|
+
/** Set the volume of this sound instance
|
|
5100
|
+
* @param {number} volume */
|
|
5101
|
+
setVolume(volume)
|
|
5102
|
+
{
|
|
5103
|
+
ASSERT(volume >= 0, 'Sound volume must be positive or zero');
|
|
5104
|
+
this.volume = volume;
|
|
5105
|
+
if (this.gainNode)
|
|
5106
|
+
this.gainNode.gain.value = volume;
|
|
5107
|
+
}
|
|
5108
|
+
|
|
5109
|
+
/** Stop this sound instance and reset position to the start */
|
|
5110
|
+
stop(fadeTime=0)
|
|
5111
|
+
{
|
|
5112
|
+
ASSERT(fadeTime >= 0, 'Sound fade time must be positive or zero');
|
|
5113
|
+
if (this.isPlaying())
|
|
5114
|
+
{
|
|
5115
|
+
if (fadeTime)
|
|
5116
|
+
{
|
|
5117
|
+
// ramp off gain
|
|
5118
|
+
const startFade = audioContext.currentTime;
|
|
5119
|
+
const endFade = startFade + fadeTime;
|
|
5120
|
+
this.gainNode.gain.linearRampToValueAtTime(1, startFade);
|
|
5121
|
+
this.gainNode.gain.linearRampToValueAtTime(0, endFade);
|
|
5122
|
+
this.source.stop(endFade);
|
|
5123
|
+
}
|
|
5124
|
+
else
|
|
5125
|
+
this.source.stop();
|
|
5126
|
+
}
|
|
5127
|
+
this.pausedTime = 0;
|
|
5128
|
+
this.source = undefined;
|
|
5129
|
+
this.startTime = undefined;
|
|
5130
|
+
}
|
|
5131
|
+
|
|
5132
|
+
/** Pause this sound instance */
|
|
5133
|
+
pause()
|
|
5134
|
+
{
|
|
5135
|
+
if (this.isPaused())
|
|
5136
|
+
return;
|
|
5137
|
+
|
|
5138
|
+
// save current time and stop sound
|
|
5139
|
+
this.pausedTime = this.getCurrentTime();
|
|
5140
|
+
this.source.stop();
|
|
5141
|
+
this.source = undefined;
|
|
5142
|
+
this.startTime = undefined;
|
|
5143
|
+
}
|
|
5144
|
+
|
|
5145
|
+
/** Unpauses this sound instance */
|
|
5146
|
+
resume()
|
|
5147
|
+
{
|
|
5148
|
+
if (!this.isPaused())
|
|
5149
|
+
return;
|
|
5150
|
+
|
|
5151
|
+
// restart sound from paused time
|
|
5152
|
+
this.start(this.pausedTime);
|
|
5153
|
+
}
|
|
5154
|
+
|
|
5155
|
+
/** Check if this instance is currently playing
|
|
5156
|
+
* @return {boolean} - True if playing
|
|
5157
|
+
*/
|
|
5158
|
+
isPlaying() { return !!this.source; }
|
|
5159
|
+
|
|
5160
|
+
/** Check if this instance is paused and was not stopped
|
|
5161
|
+
* @return {boolean} - True if paused
|
|
5162
|
+
*/
|
|
5163
|
+
isPaused() { return !this.isPlaying(); }
|
|
5164
|
+
|
|
5165
|
+
/** Get the current playback time in seconds
|
|
5166
|
+
* @return {number} - Current playback time
|
|
5167
|
+
*/
|
|
5168
|
+
getCurrentTime()
|
|
5169
|
+
{
|
|
5170
|
+
const deltaTime = mod(audioContext.currentTime - this.startTime,
|
|
5171
|
+
this.getDuration());
|
|
5172
|
+
return this.isPlaying() ? deltaTime : this.pausedTime;
|
|
4811
5173
|
}
|
|
4812
|
-
}
|
|
4813
5174
|
|
|
4814
|
-
/**
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
* @return {SoundWave} - The sound object for this file
|
|
4819
|
-
* @memberof Audio */
|
|
4820
|
-
function playAudioFile(filename, volume=1, loop=false)
|
|
4821
|
-
{
|
|
4822
|
-
if (!soundEnable || headlessMode) return;
|
|
5175
|
+
/** Get the total duration of this sound
|
|
5176
|
+
* @return {number} - Total duration in seconds
|
|
5177
|
+
*/
|
|
5178
|
+
getDuration() { return this.sound.getDuration() / this.rate; }
|
|
4823
5179
|
|
|
4824
|
-
|
|
5180
|
+
/** Get source of this sound instance
|
|
5181
|
+
* @return {AudioBufferSourceNode}
|
|
5182
|
+
*/
|
|
5183
|
+
getSource() { return this.source; }
|
|
4825
5184
|
}
|
|
4826
5185
|
|
|
4827
5186
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -4875,9 +5234,11 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
4875
5234
|
* @param {boolean} [loop] - True if the sound should loop when it reaches the end
|
|
4876
5235
|
* @param {number} [sampleRate=44100] - Sample rate for the sound
|
|
4877
5236
|
* @param {GainNode} [gainNode] - Optional gain node for volume control while playing
|
|
5237
|
+
* @param {number} [offset] - Offset in seconds to start playback from
|
|
5238
|
+
* @param {Function} [onended] - Callback for when the sound ends
|
|
4878
5239
|
* @return {AudioBufferSourceNode} - The audio node of the sound played
|
|
4879
5240
|
* @memberof Audio */
|
|
4880
|
-
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=
|
|
5241
|
+
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=audioDefaultSampleRate, gainNode, offset=0, onended)
|
|
4881
5242
|
{
|
|
4882
5243
|
if (!soundEnable || headlessMode) return;
|
|
4883
5244
|
|
|
@@ -4902,16 +5263,20 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
4902
5263
|
const pannerNode = new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)});
|
|
4903
5264
|
source.connect(pannerNode).connect(gainNode);
|
|
4904
5265
|
|
|
4905
|
-
//
|
|
4906
|
-
if (
|
|
5266
|
+
// callback when the sound ends
|
|
5267
|
+
if (onended)
|
|
5268
|
+
source.addEventListener('ended', ()=> onended(source));
|
|
5269
|
+
|
|
5270
|
+
if (!audioIsRunning())
|
|
4907
5271
|
{
|
|
4908
|
-
// fix stalled audio
|
|
4909
|
-
audioContext.resume()
|
|
5272
|
+
// fix stalled audio, this sound won't be able to play
|
|
5273
|
+
audioContext.resume();
|
|
5274
|
+
return;
|
|
4910
5275
|
}
|
|
4911
|
-
else
|
|
4912
|
-
source.start();
|
|
4913
5276
|
|
|
4914
|
-
// return sound
|
|
5277
|
+
// play and return sound
|
|
5278
|
+
const startOffset = offset * rate;
|
|
5279
|
+
source.start(0, startOffset);
|
|
4915
5280
|
return source;
|
|
4916
5281
|
}
|
|
4917
5282
|
|
|
@@ -4919,18 +5284,13 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
4919
5284
|
// ZzFXMicro - Zuper Zmall Zound Zynth - v1.3.2 by Frank Force
|
|
4920
5285
|
|
|
4921
5286
|
/** Generate and play a ZzFX sound
|
|
4922
|
-
*
|
|
5287
|
+
*
|
|
4923
5288
|
* <a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a>
|
|
4924
5289
|
* @param {Array} zzfxSound - Array of ZzFX parameters, ex. [.5,.5]
|
|
4925
5290
|
* @return {AudioBufferSourceNode} - The audio node of the sound played
|
|
4926
5291
|
* @memberof Audio */
|
|
4927
5292
|
function zzfx(...zzfxSound) { return playSamples([zzfxG(...zzfxSound)]); }
|
|
4928
5293
|
|
|
4929
|
-
/** Sample rate used for all ZzFX sounds
|
|
4930
|
-
* @default 44100
|
|
4931
|
-
* @memberof Audio */
|
|
4932
|
-
const zzfxR = 44100;
|
|
4933
|
-
|
|
4934
5294
|
/** Generate samples for a ZzFX sound
|
|
4935
5295
|
* @param {number} [volume] - Volume scale (percent)
|
|
4936
5296
|
* @param {number} [randomness] - How much to randomize frequency (percent Hz)
|
|
@@ -4954,11 +5314,10 @@ const zzfxR = 44100;
|
|
|
4954
5314
|
* @param {number} [tremolo] - Trembling effect, rate controlled by repeat time (percent)
|
|
4955
5315
|
* @param {number} [filter] - Filter cutoff frequency, positive for HPF, negative for LPF (Hz)
|
|
4956
5316
|
* @return {Array} - Array of audio samples
|
|
4957
|
-
* @memberof Audio
|
|
4958
|
-
*/
|
|
5317
|
+
* @memberof Audio */
|
|
4959
5318
|
function zzfxG
|
|
4960
5319
|
(
|
|
4961
|
-
volume = 1,
|
|
5320
|
+
volume = 1,
|
|
4962
5321
|
randomness = .05,
|
|
4963
5322
|
frequency = 220,
|
|
4964
5323
|
attack = 0,
|
|
@@ -4966,11 +5325,11 @@ function zzfxG
|
|
|
4966
5325
|
release = .1,
|
|
4967
5326
|
shape = 0,
|
|
4968
5327
|
shapeCurve = 1,
|
|
4969
|
-
slide = 0,
|
|
4970
|
-
deltaSlide = 0,
|
|
4971
|
-
pitchJump = 0,
|
|
4972
|
-
pitchJumpTime = 0,
|
|
4973
|
-
repeatTime = 0,
|
|
5328
|
+
slide = 0,
|
|
5329
|
+
deltaSlide = 0,
|
|
5330
|
+
pitchJump = 0,
|
|
5331
|
+
pitchJumpTime = 0,
|
|
5332
|
+
repeatTime = 0,
|
|
4974
5333
|
noise = 0,
|
|
4975
5334
|
modulation = 0,
|
|
4976
5335
|
bitCrush = 0,
|
|
@@ -4982,19 +5341,19 @@ function zzfxG
|
|
|
4982
5341
|
)
|
|
4983
5342
|
{
|
|
4984
5343
|
// init parameters
|
|
4985
|
-
let sampleRate =
|
|
4986
|
-
PI2 = PI*2,
|
|
5344
|
+
let sampleRate = audioDefaultSampleRate,
|
|
5345
|
+
PI2 = PI*2,
|
|
4987
5346
|
startSlide = slide *= 500 * PI2 / sampleRate / sampleRate,
|
|
4988
|
-
startFrequency = frequency *=
|
|
5347
|
+
startFrequency = frequency *=
|
|
4989
5348
|
(1 + rand(randomness,-randomness)) * PI2 / sampleRate,
|
|
4990
|
-
modOffset = 0, // modulation offset
|
|
5349
|
+
modOffset = 0, // modulation offset
|
|
4991
5350
|
repeat = 0, // repeat offset
|
|
4992
5351
|
crush = 0, // bit crush offset
|
|
4993
5352
|
jump = 1, // pitch jump timer
|
|
4994
5353
|
length, // sample length
|
|
4995
5354
|
b = [], // sample buffer
|
|
4996
5355
|
t = 0, // sample time
|
|
4997
|
-
i = 0, // sample index
|
|
5356
|
+
i = 0, // sample index
|
|
4998
5357
|
s = 0, // sample value
|
|
4999
5358
|
f, // wave frequency
|
|
5000
5359
|
|
|
@@ -5002,7 +5361,7 @@ function zzfxG
|
|
|
5002
5361
|
quality = 2, w = PI2 * abs(filter) * 2 / sampleRate,
|
|
5003
5362
|
cos = Math.cos(w), alpha = Math.sin(w) / 2 / quality,
|
|
5004
5363
|
a0 = 1 + alpha, a1 = -2*cos / a0, a2 = (1 - alpha) / a0,
|
|
5005
|
-
b0 = (1 + sign(filter) * cos) / 2 / a0,
|
|
5364
|
+
b0 = (1 + sign(filter) * cos) / 2 / a0,
|
|
5006
5365
|
b1 = -(sign(filter) + cos) / a0, b2 = b0,
|
|
5007
5366
|
x2 = 0, x1 = 0, y2 = 0, y1 = 0;
|
|
5008
5367
|
|
|
@@ -5048,7 +5407,7 @@ function zzfxG
|
|
|
5048
5407
|
0); // post release
|
|
5049
5408
|
|
|
5050
5409
|
s = delay ? s/2 + (delay > i ? 0 : // delay
|
|
5051
|
-
(i<length-delay? 1 : (length-i)/delay) * // release delay
|
|
5410
|
+
(i<length-delay? 1 : (length-i)/delay) * // release delay
|
|
5052
5411
|
b[i-delay|0]/2/volume) : s; // sample delay
|
|
5053
5412
|
|
|
5054
5413
|
if (filter) // apply filter
|
|
@@ -5060,14 +5419,14 @@ function zzfxG
|
|
|
5060
5419
|
t += f + f*noise*Math.sin(i**5); // noise
|
|
5061
5420
|
|
|
5062
5421
|
if (jump && ++jump > pitchJumpTime) // pitch jump
|
|
5063
|
-
{
|
|
5422
|
+
{
|
|
5064
5423
|
frequency += pitchJump; // apply pitch jump
|
|
5065
5424
|
startFrequency += pitchJump; // also apply to start
|
|
5066
5425
|
jump = 0; // stop pitch jump time
|
|
5067
|
-
}
|
|
5426
|
+
}
|
|
5068
5427
|
|
|
5069
5428
|
if (repeatTime && !(++repeat % repeatTime)) // repeat
|
|
5070
|
-
{
|
|
5429
|
+
{
|
|
5071
5430
|
frequency = startFrequency; // reset frequency
|
|
5072
5431
|
slide = startSlide; // reset slide
|
|
5073
5432
|
jump ||= 1; // reset pitch jump time
|
|
@@ -5076,12 +5435,11 @@ function zzfxG
|
|
|
5076
5435
|
|
|
5077
5436
|
return b; // return sample buffer
|
|
5078
5437
|
}
|
|
5079
|
-
/**
|
|
5438
|
+
/**
|
|
5080
5439
|
* LittleJS Tile Layer System
|
|
5081
5440
|
* - Caches arrays of tiles to off screen canvas for fast rendering
|
|
5082
5441
|
* - Unlimited numbers of layers, allocates canvases as needed
|
|
5083
5442
|
* - Tile layers can be drawn to using their context with canvas2d
|
|
5084
|
-
* - Drawn directly to the main canvas without using WebGL
|
|
5085
5443
|
* - Tile layers can also have collision with EngineObjects
|
|
5086
5444
|
* @namespace TileCollision
|
|
5087
5445
|
*/
|
|
@@ -5090,9 +5448,9 @@ function zzfxG
|
|
|
5090
5448
|
// Tile Layer System
|
|
5091
5449
|
|
|
5092
5450
|
/** Keep track of all tile layers with collision
|
|
5093
|
-
* @type {Array<TileCollisionLayer>}
|
|
5451
|
+
* @type {Array<TileCollisionLayer>}
|
|
5094
5452
|
* @memberof TileCollision */
|
|
5095
|
-
|
|
5453
|
+
const tileCollisionLayers = [];
|
|
5096
5454
|
|
|
5097
5455
|
/** Get tile collision data for a given cell in the grid
|
|
5098
5456
|
* @param {Vector2} pos
|
|
@@ -5146,7 +5504,7 @@ function tileCollisionRaycast(posStart, posEnd, object, solidOnly=true)
|
|
|
5146
5504
|
}
|
|
5147
5505
|
|
|
5148
5506
|
///////////////////////////////////////////////////////////////////////////////
|
|
5149
|
-
/**
|
|
5507
|
+
/**
|
|
5150
5508
|
* Load tile layers from exported data
|
|
5151
5509
|
* @param {Object} tileMapData - Level data from exported data
|
|
5152
5510
|
* @param {TileInfo} [tileInfo] - Default tile info (used for size and texture)
|
|
@@ -5179,13 +5537,13 @@ function tileCollisionLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisio
|
|
|
5179
5537
|
{
|
|
5180
5538
|
const dataLayer = tileMapData.layers[layerIndex];
|
|
5181
5539
|
ASSERT(dataLayer.data && dataLayer.data.length);
|
|
5182
|
-
ASSERT(levelSize.area()
|
|
5540
|
+
ASSERT(levelSize.area() === dataLayer.data.length);
|
|
5183
5541
|
|
|
5184
5542
|
const layerRenderOrder = renderOrder - (layerCount - 1 - layerIndex);
|
|
5185
5543
|
const tileLayer = new TileCollisionLayer(vec2(), levelSize, tileInfo, layerRenderOrder);
|
|
5186
5544
|
tileLayers[layerIndex] = tileLayer;
|
|
5187
5545
|
|
|
5188
|
-
for (let x=levelSize.x; x--;)
|
|
5546
|
+
for (let x=levelSize.x; x--;)
|
|
5189
5547
|
for (let y=levelSize.y; y--;)
|
|
5190
5548
|
{
|
|
5191
5549
|
const pos = vec2(x, levelSize.y-1-y);
|
|
@@ -5244,7 +5602,7 @@ class TileLayerData
|
|
|
5244
5602
|
/**
|
|
5245
5603
|
* Canvas Layer - cached off screen rendering system
|
|
5246
5604
|
* - Contains an offscreen canvas that can be rendered to
|
|
5247
|
-
* -
|
|
5605
|
+
* - WebGL rendering is optional, call useWebGL to enable
|
|
5248
5606
|
* @extends EngineObject
|
|
5249
5607
|
* @example
|
|
5250
5608
|
* const canvasLayer = new CanvasLayer(vec2(), vec2(200,100));
|
|
@@ -5266,18 +5624,18 @@ class CanvasLayer extends EngineObject
|
|
|
5266
5624
|
this.canvas = headlessMode ? undefined : new OffscreenCanvas(canvasSize.x, canvasSize.y);
|
|
5267
5625
|
/** @property {OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this layer */
|
|
5268
5626
|
this.context = headlessMode ? undefined : this.canvas.getContext('2d');
|
|
5269
|
-
/** @property {WebGLTexture} - Texture if using
|
|
5627
|
+
/** @property {WebGLTexture} - Texture if using WebGL for this layer, call useWebGL to enable */
|
|
5270
5628
|
this.glTexture = undefined;
|
|
5271
5629
|
this.gravityScale = 0; // disable gravity by default for canvas layers
|
|
5272
5630
|
}
|
|
5273
|
-
|
|
5631
|
+
|
|
5274
5632
|
/** Destroy this canvas layer */
|
|
5275
5633
|
destroy()
|
|
5276
5634
|
{
|
|
5277
5635
|
if (this.destroyed)
|
|
5278
5636
|
return;
|
|
5279
5637
|
|
|
5280
|
-
// free up the
|
|
5638
|
+
// free up the WebGL texture
|
|
5281
5639
|
if (this.glTexture)
|
|
5282
5640
|
glDeleteTexture(this.glTexture);
|
|
5283
5641
|
super.destroy();
|
|
@@ -5302,12 +5660,12 @@ class CanvasLayer extends EngineObject
|
|
|
5302
5660
|
draw(pos, size, angle=0, color=WHITE, mirror=false, additiveColor, screenSpace=false, context)
|
|
5303
5661
|
{
|
|
5304
5662
|
// draw the canvas layer as a single tile that uses the whole texture
|
|
5305
|
-
const
|
|
5663
|
+
const useWebGL = glEnable && this.glTexture !== undefined;
|
|
5306
5664
|
const tileInfo = new TileInfo().setFullImage(this.canvas, this.glTexture);
|
|
5307
|
-
drawTile(pos, size, tileInfo, color, angle, mirror, additiveColor,
|
|
5665
|
+
drawTile(pos, size, tileInfo, color, angle, mirror, additiveColor, useWebGL, screenSpace, context);
|
|
5308
5666
|
}
|
|
5309
5667
|
|
|
5310
|
-
/** Draw onto the layer canvas in world space (bypass
|
|
5668
|
+
/** Draw onto the layer canvas in world space (bypass WebGL)
|
|
5311
5669
|
* @param {Vector2} pos
|
|
5312
5670
|
* @param {Vector2} size
|
|
5313
5671
|
* @param {number} angle
|
|
@@ -5341,8 +5699,8 @@ class CanvasLayer extends EngineObject
|
|
|
5341
5699
|
if (textureInfo)
|
|
5342
5700
|
{
|
|
5343
5701
|
context.globalAlpha = color.a; // only alpha is supported
|
|
5344
|
-
context.drawImage(textureInfo.image,
|
|
5345
|
-
tileInfo.pos.x, tileInfo.pos.y,
|
|
5702
|
+
context.drawImage(textureInfo.image,
|
|
5703
|
+
tileInfo.pos.x, tileInfo.pos.y,
|
|
5346
5704
|
tileInfo.size.x, tileInfo.size.y, -.5, -.5, 1, 1);
|
|
5347
5705
|
context.globalAlpha = 1;
|
|
5348
5706
|
}
|
|
@@ -5360,11 +5718,11 @@ class CanvasLayer extends EngineObject
|
|
|
5360
5718
|
* @param {Vector2} [size=(1,1)]
|
|
5361
5719
|
* @param {Color} [color=(1,1,1,1)]
|
|
5362
5720
|
* @param {number} [angle=0] */
|
|
5363
|
-
drawRect(pos, size, color, angle)
|
|
5721
|
+
drawRect(pos, size, color, angle)
|
|
5364
5722
|
{ this.drawTile(pos, size, undefined, color, angle); }
|
|
5365
5723
|
|
|
5366
|
-
/** Create or update the
|
|
5367
|
-
* @param {boolean} [enable] - enable
|
|
5724
|
+
/** Create or update the WebGL texture for this layer
|
|
5725
|
+
* @param {boolean} [enable] - enable WebGL rendering and update the texture */
|
|
5368
5726
|
useWebGL(enable=true)
|
|
5369
5727
|
{
|
|
5370
5728
|
if (glEnable && enable)
|
|
@@ -5410,7 +5768,7 @@ class TileLayer extends CanvasLayer
|
|
|
5410
5768
|
this.canvas = new OffscreenCanvas(canvasSize.x, canvasSize.y);
|
|
5411
5769
|
/** @property {OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
|
|
5412
5770
|
this.context = this.canvas.getContext('2d');
|
|
5413
|
-
/** @property {WebGLTexture} - Texture if using
|
|
5771
|
+
/** @property {WebGLTexture} - Texture if using WebGL for this layer */
|
|
5414
5772
|
this.glTexture = useWebGL ? glCreateTexture(this.canvas) : undefined;
|
|
5415
5773
|
// set no friction by default, applied friction is max of both objects
|
|
5416
5774
|
this.friction = 0;
|
|
@@ -5435,7 +5793,7 @@ class TileLayer extends CanvasLayer
|
|
|
5435
5793
|
}
|
|
5436
5794
|
}
|
|
5437
5795
|
|
|
5438
|
-
/** Set data at a given position in the array
|
|
5796
|
+
/** Set data at a given position in the array
|
|
5439
5797
|
* @param {Vector2} layerPos - Local position in array
|
|
5440
5798
|
* @param {TileLayerData} data - Data to set
|
|
5441
5799
|
* @param {boolean} [redraw] - Force the tile to redraw if true */
|
|
@@ -5447,26 +5805,26 @@ class TileLayer extends CanvasLayer
|
|
|
5447
5805
|
redraw && this.drawTileData(layerPos);
|
|
5448
5806
|
}
|
|
5449
5807
|
}
|
|
5450
|
-
|
|
5451
|
-
/** Get data at a given position in the array
|
|
5808
|
+
|
|
5809
|
+
/** Get data at a given position in the array
|
|
5452
5810
|
* @param {Vector2} layerPos - Local position in array
|
|
5453
5811
|
* @return {TileLayerData} */
|
|
5454
5812
|
getData(layerPos)
|
|
5455
5813
|
{ return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0]; }
|
|
5456
|
-
|
|
5814
|
+
|
|
5457
5815
|
// Render the tile layer, called automatically by the engine
|
|
5458
5816
|
render()
|
|
5459
5817
|
{
|
|
5460
|
-
ASSERT(drawContext
|
|
5461
|
-
|
|
5818
|
+
ASSERT(drawContext !== this.context, 'must call redrawEnd() after drawing tiles!');
|
|
5819
|
+
|
|
5462
5820
|
// draw the tile layer as a single tile
|
|
5463
5821
|
const tileInfo = new TileInfo().setFullImage(this.canvas, this.glTexture);
|
|
5464
5822
|
const pos = this.pos.add(this.size.scale(.5));
|
|
5465
|
-
const
|
|
5466
|
-
drawTile(pos, this.size, tileInfo, WHITE, 0, false, CLEAR_BLACK,
|
|
5823
|
+
const useWebGL = glEnable && this.glTexture !== undefined;
|
|
5824
|
+
drawTile(pos, this.size, tileInfo, WHITE, 0, false, CLEAR_BLACK, useWebGL);
|
|
5467
5825
|
}
|
|
5468
5826
|
|
|
5469
|
-
/** Draw all the tile data to an offscreen canvas
|
|
5827
|
+
/** Draw all the tile data to an offscreen canvas
|
|
5470
5828
|
* - This may be slow in some browsers but only needs to be done once */
|
|
5471
5829
|
redraw()
|
|
5472
5830
|
{
|
|
@@ -5476,7 +5834,7 @@ class TileLayer extends CanvasLayer
|
|
|
5476
5834
|
this.drawTileData(vec2(x,y), false);
|
|
5477
5835
|
this.redrawEnd();
|
|
5478
5836
|
if (this.glTexture)
|
|
5479
|
-
this.useWebGL(); // update
|
|
5837
|
+
this.useWebGL(); // update WebGL texture
|
|
5480
5838
|
}
|
|
5481
5839
|
|
|
5482
5840
|
/** Call to start the redraw process
|
|
@@ -5512,7 +5870,7 @@ class TileLayer extends CanvasLayer
|
|
|
5512
5870
|
/** Call to end the redraw process */
|
|
5513
5871
|
redrawEnd()
|
|
5514
5872
|
{
|
|
5515
|
-
ASSERT(drawContext
|
|
5873
|
+
ASSERT(drawContext === this.context, 'must call redrawStart() before drawing tiles');
|
|
5516
5874
|
glCopyToContext(drawContext);
|
|
5517
5875
|
//debugSaveCanvas(this.canvas);
|
|
5518
5876
|
|
|
@@ -5523,7 +5881,7 @@ class TileLayer extends CanvasLayer
|
|
|
5523
5881
|
/** Draw the tile at a given position in the tile grid
|
|
5524
5882
|
* This can be used to clear out tiles when they are destroyed
|
|
5525
5883
|
* Tiles can also be redrawn if inside a redrawStart/End block
|
|
5526
|
-
* @param {Vector2} layerPos
|
|
5884
|
+
* @param {Vector2} layerPos
|
|
5527
5885
|
* @param {boolean} [clear] - should the old tile be cleared out
|
|
5528
5886
|
*/
|
|
5529
5887
|
drawTileData(layerPos, clear=true)
|
|
@@ -5538,9 +5896,9 @@ class TileLayer extends CanvasLayer
|
|
|
5538
5896
|
|
|
5539
5897
|
// draw the tile if it has layer data
|
|
5540
5898
|
const d = this.getData(layerPos);
|
|
5541
|
-
if (d.tile
|
|
5899
|
+
if (d.tile !== undefined)
|
|
5542
5900
|
{
|
|
5543
|
-
ASSERT(drawContext
|
|
5901
|
+
ASSERT(drawContext === this.context, 'must call redrawStart() before drawing tiles');
|
|
5544
5902
|
const pos = layerPos.add(vec2(.5));
|
|
5545
5903
|
const tileInfo = tile(d.tile, s, this.tileInfo.textureIndex, this.tileInfo.padding);
|
|
5546
5904
|
drawTile(pos, vec2(1), tileInfo, d.color, d.direction*PI/2, d.mirror);
|
|
@@ -5553,7 +5911,7 @@ class TileLayer extends CanvasLayer
|
|
|
5553
5911
|
* Tile Collision Layer - a tile layer with collision
|
|
5554
5912
|
* - adds collision data and functions to TileLayer
|
|
5555
5913
|
* - there can be multiple tile collision layers
|
|
5556
|
-
* - tile
|
|
5914
|
+
* - tile collision layers should not overlap each other
|
|
5557
5915
|
* @extends TileLayer
|
|
5558
5916
|
*/
|
|
5559
5917
|
class TileCollisionLayer extends TileLayer
|
|
@@ -5704,7 +6062,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
5704
6062
|
debugRaycast && debugLine(posStart, posEnd, '#00f', .02);
|
|
5705
6063
|
}
|
|
5706
6064
|
}
|
|
5707
|
-
/**
|
|
6065
|
+
/**
|
|
5708
6066
|
* LittleJS Particle System
|
|
5709
6067
|
*/
|
|
5710
6068
|
|
|
@@ -5721,7 +6079,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
5721
6079
|
* rgb(1,1,1,1), rgb(0,0,0,1), // colorStartA, colorStartB
|
|
5722
6080
|
* rgb(1,1,1,0), rgb(0,0,0,0), // colorEndA, colorEndB
|
|
5723
6081
|
* 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
|
|
5724
|
-
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
|
|
6082
|
+
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
|
|
5725
6083
|
* .5, 1 // randomness, collide, additive, randomColorLinear, renderOrder
|
|
5726
6084
|
* );
|
|
5727
6085
|
*/
|
|
@@ -5729,35 +6087,35 @@ class ParticleEmitter extends EngineObject
|
|
|
5729
6087
|
{
|
|
5730
6088
|
/** Create a particle system with the given settings
|
|
5731
6089
|
* @param {Vector2} position - World space position of the emitter
|
|
5732
|
-
* @param {
|
|
5733
|
-
* @param {
|
|
5734
|
-
* @param {
|
|
5735
|
-
* @param {
|
|
5736
|
-
* @param {
|
|
6090
|
+
* @param {number} [angle] - Angle to emit the particles
|
|
6091
|
+
* @param {number|Vector2} [emitSize] - World space size of the emitter (float for circle diameter, vec2 for rect)
|
|
6092
|
+
* @param {number} [emitTime] - How long to stay alive (0 is forever)
|
|
6093
|
+
* @param {number} [emitRate] - How many particles per second to spawn, does not emit if 0
|
|
6094
|
+
* @param {number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter
|
|
5737
6095
|
* @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
|
|
5738
6096
|
* @param {Color} [colorStartA=(1,1,1,1)] - Color at start of life 1, randomized between start colors
|
|
5739
6097
|
* @param {Color} [colorStartB=(1,1,1,1)] - Color at start of life 2, randomized between start colors
|
|
5740
6098
|
* @param {Color} [colorEndA=(1,1,1,0)] - Color at end of life 1, randomized between end colors
|
|
5741
6099
|
* @param {Color} [colorEndB=(1,1,1,0)] - Color at end of life 2, randomized between end colors
|
|
5742
|
-
* @param {
|
|
5743
|
-
* @param {
|
|
5744
|
-
* @param {
|
|
5745
|
-
* @param {
|
|
5746
|
-
* @param {
|
|
5747
|
-
* @param {
|
|
5748
|
-
* @param {
|
|
5749
|
-
* @param {
|
|
5750
|
-
* @param {
|
|
5751
|
-
* @param {
|
|
5752
|
-
* @param {
|
|
6100
|
+
* @param {number} [particleTime] - How long particles live
|
|
6101
|
+
* @param {number} [sizeStart] - How big are particles at start
|
|
6102
|
+
* @param {number} [sizeEnd] - How big are particles at end
|
|
6103
|
+
* @param {number} [speed] - How fast are particles when spawned
|
|
6104
|
+
* @param {number} [angleSpeed] - How fast are particles rotating
|
|
6105
|
+
* @param {number} [damping] - How much to dampen particle speed
|
|
6106
|
+
* @param {number} [angleDamping] - How much to dampen particle angular speed
|
|
6107
|
+
* @param {number} [gravityScale] - How much gravity effect particles
|
|
6108
|
+
* @param {number} [particleConeAngle] - Cone for start particle angle
|
|
6109
|
+
* @param {number} [fadeRate] - How quick to fade particles at start/end in percent of life
|
|
6110
|
+
* @param {number} [randomness] - Apply extra randomness percent
|
|
5753
6111
|
* @param {boolean} [collideTiles] - Do particles collide against tiles
|
|
5754
6112
|
* @param {boolean} [additive] - Should particles use additive blend
|
|
5755
6113
|
* @param {boolean} [randomColorLinear] - Should color be randomized linearly or across each component
|
|
5756
|
-
* @param {
|
|
6114
|
+
* @param {number} [renderOrder] - Render order for particles (additive is above other stuff by default)
|
|
5757
6115
|
* @param {boolean} [localSpace] - Should it be in local space of emitter (world space is default)
|
|
5758
6116
|
*/
|
|
5759
6117
|
constructor
|
|
5760
|
-
(
|
|
6118
|
+
(
|
|
5761
6119
|
position,
|
|
5762
6120
|
angle,
|
|
5763
6121
|
emitSize = 0,
|
|
@@ -5779,7 +6137,7 @@ class ParticleEmitter extends EngineObject
|
|
|
5779
6137
|
gravityScale = 0,
|
|
5780
6138
|
particleConeAngle = PI,
|
|
5781
6139
|
fadeRate = .1,
|
|
5782
|
-
randomness = .2,
|
|
6140
|
+
randomness = .2,
|
|
5783
6141
|
collideTiles = false,
|
|
5784
6142
|
additive = false,
|
|
5785
6143
|
randomColorLinear = true,
|
|
@@ -5790,13 +6148,13 @@ class ParticleEmitter extends EngineObject
|
|
|
5790
6148
|
super(position, vec2(), tileInfo, angle, undefined, renderOrder);
|
|
5791
6149
|
|
|
5792
6150
|
// emitter settings
|
|
5793
|
-
/** @property {
|
|
6151
|
+
/** @property {number|Vector2} - World space size of the emitter (float for circle diameter, vec2 for rect) */
|
|
5794
6152
|
this.emitSize = emitSize
|
|
5795
|
-
/** @property {
|
|
6153
|
+
/** @property {number} - How long to stay alive (0 is forever) */
|
|
5796
6154
|
this.emitTime = emitTime;
|
|
5797
|
-
/** @property {
|
|
6155
|
+
/** @property {number} - How many particles per second to spawn, does not emit if 0 */
|
|
5798
6156
|
this.emitRate = emitRate;
|
|
5799
|
-
/** @property {
|
|
6157
|
+
/** @property {number} - Local angle to apply velocity to particles from emitter */
|
|
5800
6158
|
this.emitConeAngle = emitConeAngle;
|
|
5801
6159
|
|
|
5802
6160
|
// color settings
|
|
@@ -5812,27 +6170,27 @@ class ParticleEmitter extends EngineObject
|
|
|
5812
6170
|
this.randomColorLinear = randomColorLinear;
|
|
5813
6171
|
|
|
5814
6172
|
// particle settings
|
|
5815
|
-
/** @property {
|
|
6173
|
+
/** @property {number} - How long particles live */
|
|
5816
6174
|
this.particleTime = particleTime;
|
|
5817
|
-
/** @property {
|
|
6175
|
+
/** @property {number} - How big are particles at start */
|
|
5818
6176
|
this.sizeStart = sizeStart;
|
|
5819
|
-
/** @property {
|
|
6177
|
+
/** @property {number} - How big are particles at end */
|
|
5820
6178
|
this.sizeEnd = sizeEnd;
|
|
5821
|
-
/** @property {
|
|
6179
|
+
/** @property {number} - How fast are particles when spawned */
|
|
5822
6180
|
this.speed = speed;
|
|
5823
|
-
/** @property {
|
|
6181
|
+
/** @property {number} - How fast are particles rotating */
|
|
5824
6182
|
this.angleSpeed = angleSpeed;
|
|
5825
|
-
/** @property {
|
|
6183
|
+
/** @property {number} - How much to dampen particle speed */
|
|
5826
6184
|
this.damping = damping;
|
|
5827
|
-
/** @property {
|
|
6185
|
+
/** @property {number} - How much to dampen particle angular speed */
|
|
5828
6186
|
this.angleDamping = angleDamping;
|
|
5829
|
-
/** @property {
|
|
6187
|
+
/** @property {number} - How much gravity affects particles */
|
|
5830
6188
|
this.gravityScale = gravityScale;
|
|
5831
|
-
/** @property {
|
|
6189
|
+
/** @property {number} - Cone for start particle angle */
|
|
5832
6190
|
this.particleConeAngle = particleConeAngle;
|
|
5833
|
-
/** @property {
|
|
6191
|
+
/** @property {number} - How quick to fade in particles at start/end in percent of life */
|
|
5834
6192
|
this.fadeRate = fadeRate;
|
|
5835
|
-
/** @property {
|
|
6193
|
+
/** @property {number} - Apply extra randomness percent */
|
|
5836
6194
|
this.randomness = randomness;
|
|
5837
6195
|
/** @property {boolean} - Do particles collide against tiles */
|
|
5838
6196
|
this.collideTiles = collideTiles;
|
|
@@ -5840,16 +6198,16 @@ class ParticleEmitter extends EngineObject
|
|
|
5840
6198
|
this.additive = additive;
|
|
5841
6199
|
/** @property {boolean} - Should it be in local space of emitter */
|
|
5842
6200
|
this.localSpace = localSpace;
|
|
5843
|
-
/** @property {
|
|
6201
|
+
/** @property {number} - If non zero the particle is drawn as a trail, stretched in the direction of velocity */
|
|
5844
6202
|
this.trailScale = 0;
|
|
5845
6203
|
/** @property {Function} - Callback when particle is destroyed */
|
|
5846
6204
|
this.particleDestroyCallback = undefined;
|
|
5847
6205
|
/** @property {Function} - Callback when particle is created */
|
|
5848
6206
|
this.particleCreateCallback = undefined;
|
|
5849
|
-
/** @property {
|
|
6207
|
+
/** @property {number} - Track particle emit time */
|
|
5850
6208
|
this.emitTimeBuffer = 0;
|
|
5851
6209
|
}
|
|
5852
|
-
|
|
6210
|
+
|
|
5853
6211
|
/** Update the emitter to spawn particles, called automatically by engine once each frame */
|
|
5854
6212
|
update()
|
|
5855
6213
|
{
|
|
@@ -5873,7 +6231,7 @@ class ParticleEmitter extends EngineObject
|
|
|
5873
6231
|
if (debugParticles)
|
|
5874
6232
|
{
|
|
5875
6233
|
// show emitter bounds
|
|
5876
|
-
const emitSize = typeof this.emitSize
|
|
6234
|
+
const emitSize = typeof this.emitSize === 'number' ? vec2(this.emitSize) : this.emitSize;
|
|
5877
6235
|
debugRect(this.pos, emitSize, '#0f0', 0, this.angle);
|
|
5878
6236
|
}
|
|
5879
6237
|
}
|
|
@@ -5883,7 +6241,7 @@ class ParticleEmitter extends EngineObject
|
|
|
5883
6241
|
emitParticle()
|
|
5884
6242
|
{
|
|
5885
6243
|
// spawn a particle
|
|
5886
|
-
let pos = typeof this.emitSize
|
|
6244
|
+
let pos = typeof this.emitSize === 'number' ? // check if number was used
|
|
5887
6245
|
randInCircle(this.emitSize/2) // circle emitter
|
|
5888
6246
|
: vec2(rand(-.5,.5), rand(-.5,.5)) // box emitter
|
|
5889
6247
|
.multiply(this.emitSize).rotate(this.angle)
|
|
@@ -5908,7 +6266,7 @@ class ParticleEmitter extends EngineObject
|
|
|
5908
6266
|
const colorStart = randColor(this.colorStartA, this.colorStartB, this.randomColorLinear);
|
|
5909
6267
|
const colorEnd = randColor(this.colorEndA, this.colorEndB, this.randomColorLinear);
|
|
5910
6268
|
const velocityAngle = this.localSpace ? coneAngle : this.angle + coneAngle;
|
|
5911
|
-
|
|
6269
|
+
|
|
5912
6270
|
// build particle
|
|
5913
6271
|
const particle = new Particle(pos, this.tileInfo, angle, colorStart, colorEnd, particleTime, sizeStart, sizeEnd, this.fadeRate, this.additive, this.trailScale, this.localSpace && this, this.particleDestroyCallback);
|
|
5914
6272
|
particle.velocity = vec2().setAngle(velocityAngle, speed);
|
|
@@ -5953,38 +6311,38 @@ class Particle extends EngineObject
|
|
|
5953
6311
|
* Typically this is created automatically by a ParticleEmitter
|
|
5954
6312
|
* @param {Vector2} position - World space position of the particle
|
|
5955
6313
|
* @param {TileInfo} tileInfo - Tile info to render particles
|
|
5956
|
-
* @param {
|
|
6314
|
+
* @param {number} angle - Angle to rotate the particle
|
|
5957
6315
|
* @param {Color} colorStart - Color at start of life
|
|
5958
6316
|
* @param {Color} colorEnd - Color at end of life
|
|
5959
|
-
* @param {
|
|
5960
|
-
* @param {
|
|
5961
|
-
* @param {
|
|
5962
|
-
* @param {
|
|
6317
|
+
* @param {number} lifeTime - How long to live for
|
|
6318
|
+
* @param {number} sizeStart - Size at start of life
|
|
6319
|
+
* @param {number} sizeEnd - Size at end of life
|
|
6320
|
+
* @param {number} fadeRate - How quick to fade in/out
|
|
5963
6321
|
* @param {boolean} additive - Does it use additive blend mode
|
|
5964
|
-
* @param {
|
|
6322
|
+
* @param {number} trailScale - If a trail, how long to make it
|
|
5965
6323
|
* @param {ParticleEmitter} [localSpaceEmitter] - Parent emitter if local space
|
|
5966
6324
|
* @param {Function} [destroyCallback] - Callback when particle dies
|
|
5967
6325
|
*/
|
|
5968
6326
|
constructor(position, tileInfo, angle, colorStart, colorEnd, lifeTime, sizeStart, sizeEnd, fadeRate, additive, trailScale, localSpaceEmitter, destroyCallback
|
|
5969
6327
|
)
|
|
5970
|
-
{
|
|
5971
|
-
super(position, vec2(), tileInfo, angle);
|
|
5972
|
-
|
|
6328
|
+
{
|
|
6329
|
+
super(position, vec2(), tileInfo, angle);
|
|
6330
|
+
|
|
5973
6331
|
/** @property {Color} - Color at start of life */
|
|
5974
6332
|
this.colorStart = colorStart;
|
|
5975
6333
|
/** @property {Color} - Calculated change in color */
|
|
5976
6334
|
this.colorEndDelta = colorEnd.subtract(colorStart);
|
|
5977
|
-
/** @property {
|
|
6335
|
+
/** @property {number} - How long to live for */
|
|
5978
6336
|
this.lifeTime = lifeTime;
|
|
5979
|
-
/** @property {
|
|
6337
|
+
/** @property {number} - Size at start of life */
|
|
5980
6338
|
this.sizeStart = sizeStart;
|
|
5981
|
-
/** @property {
|
|
6339
|
+
/** @property {number} - Calculated change in size */
|
|
5982
6340
|
this.sizeEndDelta = sizeEnd - sizeStart;
|
|
5983
|
-
/** @property {
|
|
6341
|
+
/** @property {number} - How quick to fade in/out */
|
|
5984
6342
|
this.fadeRate = fadeRate;
|
|
5985
6343
|
/** @property {boolean} - Is it additive */
|
|
5986
6344
|
this.additive = additive;
|
|
5987
|
-
/** @property {
|
|
6345
|
+
/** @property {number} - If a trail, how long to make it */
|
|
5988
6346
|
this.trailScale = trailScale;
|
|
5989
6347
|
/** @property {ParticleEmitter} - Parent emitter if local space */
|
|
5990
6348
|
this.localSpaceEmitter = localSpaceEmitter;
|
|
@@ -6024,7 +6382,7 @@ class Particle extends EngineObject
|
|
|
6024
6382
|
this.colorStart.r + p * this.colorEndDelta.r,
|
|
6025
6383
|
this.colorStart.g + p * this.colorEndDelta.g,
|
|
6026
6384
|
this.colorStart.b + p * this.colorEndDelta.b,
|
|
6027
|
-
(this.colorStart.a + p * this.colorEndDelta.a) *
|
|
6385
|
+
(this.colorStart.a + p * this.colorEndDelta.a) *
|
|
6028
6386
|
(p < fadeRate ? p/fadeRate : p > 1-fadeRate ? (1-p)/fadeRate : 1)); // fade alpha
|
|
6029
6387
|
|
|
6030
6388
|
// draw the particle
|
|
@@ -6034,7 +6392,7 @@ class Particle extends EngineObject
|
|
|
6034
6392
|
if (this.localSpaceEmitter)
|
|
6035
6393
|
{
|
|
6036
6394
|
// in local space of emitter
|
|
6037
|
-
pos = this.localSpaceEmitter.pos.add(pos.rotate(-this.localSpaceEmitter.angle));
|
|
6395
|
+
pos = this.localSpaceEmitter.pos.add(pos.rotate(-this.localSpaceEmitter.angle));
|
|
6038
6396
|
angle += this.localSpaceEmitter.angle;
|
|
6039
6397
|
}
|
|
6040
6398
|
if (this.trailScale)
|
|
@@ -6058,7 +6416,7 @@ class Particle extends EngineObject
|
|
|
6058
6416
|
this.additive && setBlendMode();
|
|
6059
6417
|
debugParticles && debugRect(pos, size, '#f005', 0, angle);
|
|
6060
6418
|
|
|
6061
|
-
if (p
|
|
6419
|
+
if (p === 1)
|
|
6062
6420
|
{
|
|
6063
6421
|
// destroy particle when it's time runs out
|
|
6064
6422
|
this.color = color;
|
|
@@ -6068,7 +6426,7 @@ class Particle extends EngineObject
|
|
|
6068
6426
|
}
|
|
6069
6427
|
}
|
|
6070
6428
|
}
|
|
6071
|
-
/**
|
|
6429
|
+
/**
|
|
6072
6430
|
* LittleJS Medal System
|
|
6073
6431
|
* - Tracks and displays medals
|
|
6074
6432
|
* - Saves medals to local storage
|
|
@@ -6089,7 +6447,7 @@ let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimeLast;
|
|
|
6089
6447
|
/** Initialize medals with a save name used for storage
|
|
6090
6448
|
* - Call this after creating all medals
|
|
6091
6449
|
* - Checks if medals are unlocked
|
|
6092
|
-
* @param {
|
|
6450
|
+
* @param {string} saveName
|
|
6093
6451
|
* @memberof Medals */
|
|
6094
6452
|
function medalsInit(saveName)
|
|
6095
6453
|
{
|
|
@@ -6104,7 +6462,7 @@ function medalsInit(saveName)
|
|
|
6104
6462
|
{
|
|
6105
6463
|
if (!medalsDisplayQueue.length)
|
|
6106
6464
|
return;
|
|
6107
|
-
|
|
6465
|
+
|
|
6108
6466
|
// update first medal in queue
|
|
6109
6467
|
const medal = medalsDisplayQueue[0];
|
|
6110
6468
|
const time = timeReal - medalsDisplayTimeLast;
|
|
@@ -6119,7 +6477,7 @@ function medalsInit(saveName)
|
|
|
6119
6477
|
{
|
|
6120
6478
|
// slide on/off medals
|
|
6121
6479
|
const slideOffTime = medalDisplayTime - medalDisplaySlideTime;
|
|
6122
|
-
const hidePercent =
|
|
6480
|
+
const hidePercent =
|
|
6123
6481
|
time < medalDisplaySlideTime ? 1 - time / medalDisplaySlideTime :
|
|
6124
6482
|
time > slideOffTime ? (time - slideOffTime) / medalDisplaySlideTime : 0;
|
|
6125
6483
|
medal.render(hidePercent);
|
|
@@ -6135,43 +6493,43 @@ function medalsForEach(callback)
|
|
|
6135
6493
|
|
|
6136
6494
|
///////////////////////////////////////////////////////////////////////////////
|
|
6137
6495
|
|
|
6138
|
-
/**
|
|
6139
|
-
* Medal - Tracks an unlockable medal
|
|
6496
|
+
/**
|
|
6497
|
+
* Medal - Tracks an unlockable medal
|
|
6140
6498
|
* @example
|
|
6141
6499
|
* // create a medal
|
|
6142
6500
|
* const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️');
|
|
6143
|
-
*
|
|
6501
|
+
*
|
|
6144
6502
|
* // initialize medals
|
|
6145
6503
|
* medalsInit('Example Game');
|
|
6146
|
-
*
|
|
6504
|
+
*
|
|
6147
6505
|
* // unlock the medal
|
|
6148
6506
|
* medal_example.unlock();
|
|
6149
6507
|
*/
|
|
6150
6508
|
class Medal
|
|
6151
6509
|
{
|
|
6152
6510
|
/** Create a medal object and adds it to the list of medals
|
|
6153
|
-
* @param {
|
|
6154
|
-
* @param {
|
|
6155
|
-
* @param {
|
|
6156
|
-
* @param {
|
|
6157
|
-
* @param {
|
|
6511
|
+
* @param {number} id - The unique identifier of the medal
|
|
6512
|
+
* @param {string} name - Name of the medal
|
|
6513
|
+
* @param {string} [description] - Description of the medal
|
|
6514
|
+
* @param {string} [icon] - Icon for the medal
|
|
6515
|
+
* @param {string} [src] - Image location for the medal
|
|
6158
6516
|
*/
|
|
6159
6517
|
constructor(id, name, description='', icon='🏆', src)
|
|
6160
6518
|
{
|
|
6161
6519
|
ASSERT(id >= 0 && !medals[id]);
|
|
6162
|
-
|
|
6163
|
-
/** @property {
|
|
6520
|
+
|
|
6521
|
+
/** @property {number} - The unique identifier of the medal */
|
|
6164
6522
|
this.id = id;
|
|
6165
|
-
|
|
6166
|
-
/** @property {
|
|
6523
|
+
|
|
6524
|
+
/** @property {string} - Name of the medal */
|
|
6167
6525
|
this.name = name;
|
|
6168
|
-
|
|
6169
|
-
/** @property {
|
|
6526
|
+
|
|
6527
|
+
/** @property {string} - Description of the medal */
|
|
6170
6528
|
this.description = description;
|
|
6171
|
-
|
|
6172
|
-
/** @property {
|
|
6529
|
+
|
|
6530
|
+
/** @property {string} - Icon for the medal */
|
|
6173
6531
|
this.icon = icon;
|
|
6174
|
-
|
|
6532
|
+
|
|
6175
6533
|
/** @property {boolean} - Is the medal unlocked? */
|
|
6176
6534
|
this.unlocked = false;
|
|
6177
6535
|
|
|
@@ -6196,7 +6554,7 @@ class Medal
|
|
|
6196
6554
|
}
|
|
6197
6555
|
|
|
6198
6556
|
/** Render a medal
|
|
6199
|
-
* @param {
|
|
6557
|
+
* @param {number} [hidePercent] - How much to slide the medal off screen
|
|
6200
6558
|
*/
|
|
6201
6559
|
render(hidePercent=0)
|
|
6202
6560
|
{
|
|
@@ -6237,7 +6595,7 @@ class Medal
|
|
|
6237
6595
|
|
|
6238
6596
|
/** Render the icon for a medal
|
|
6239
6597
|
* @param {Vector2} pos - Screen space position
|
|
6240
|
-
* @param {
|
|
6598
|
+
* @param {number} size - Screen space size
|
|
6241
6599
|
*/
|
|
6242
6600
|
renderIcon(pos, size)
|
|
6243
6601
|
{
|
|
@@ -6247,14 +6605,14 @@ class Medal
|
|
|
6247
6605
|
else
|
|
6248
6606
|
drawTextScreen(this.icon, pos, size*.7, BLACK);
|
|
6249
6607
|
}
|
|
6250
|
-
|
|
6608
|
+
|
|
6251
6609
|
// Get local storage key used by the medal
|
|
6252
6610
|
storageKey() { return medalsSaveName + '_' + this.id; }
|
|
6253
6611
|
}
|
|
6254
6612
|
/**
|
|
6255
6613
|
* LittleJS WebGL Interface
|
|
6256
|
-
* - All
|
|
6257
|
-
* - Will fall back to 2D canvas rendering if
|
|
6614
|
+
* - All WebGL used by the engine is wrapped up here
|
|
6615
|
+
* - Will fall back to 2D canvas rendering if WebGL is not supported
|
|
6258
6616
|
* - For normal stuff you won't need to see or call anything in this file
|
|
6259
6617
|
* - For advanced stuff there are helper functions to create shaders, textures, etc
|
|
6260
6618
|
* - Can be disabled with glEnable to revert to 2D canvas rendering
|
|
@@ -6269,24 +6627,27 @@ class Medal
|
|
|
6269
6627
|
* @memberof WebGL */
|
|
6270
6628
|
let glCanvas;
|
|
6271
6629
|
|
|
6272
|
-
/**
|
|
6630
|
+
/** WebGL2 context for `glCanvas`
|
|
6273
6631
|
* @type {WebGL2RenderingContext}
|
|
6274
6632
|
* @memberof WebGL */
|
|
6275
6633
|
let glContext;
|
|
6276
6634
|
|
|
6277
|
-
/** Should
|
|
6635
|
+
/** Should WebGL be setup with anti-aliasing? must be set before calling engineInit
|
|
6278
6636
|
* @type {boolean}
|
|
6279
6637
|
* @memberof WebGL */
|
|
6280
6638
|
let glAntialias = true;
|
|
6281
6639
|
|
|
6282
6640
|
// WebGL internal variables not exposed to documentation
|
|
6283
|
-
let glShader, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData,
|
|
6641
|
+
let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount;
|
|
6284
6642
|
|
|
6285
|
-
// WebGL internal constants
|
|
6286
|
-
const
|
|
6643
|
+
// WebGL internal constants
|
|
6644
|
+
const gl_ARRAY_BUFFER_SIZE = 5e5;
|
|
6287
6645
|
const gl_INDICES_PER_INSTANCE = 11;
|
|
6288
6646
|
const gl_INSTANCE_BYTE_STRIDE = gl_INDICES_PER_INSTANCE * 4;
|
|
6289
|
-
const
|
|
6647
|
+
const gl_MAX_INSTANCES = gl_ARRAY_BUFFER_SIZE / gl_INSTANCE_BYTE_STRIDE | 0;
|
|
6648
|
+
const gl_INDICES_PER_POLY_VERTEX = 3;
|
|
6649
|
+
const gl_POLY_VERTEX_BYTE_STRIDE = gl_INDICES_PER_POLY_VERTEX * 4;
|
|
6650
|
+
const gl_MAX_POLY_VERTEXES = gl_ARRAY_BUFFER_SIZE / gl_POLY_VERTEX_BYTE_STRIDE | 0;
|
|
6290
6651
|
|
|
6291
6652
|
///////////////////////////////////////////////////////////////////////////////
|
|
6292
6653
|
|
|
@@ -6307,11 +6668,11 @@ function glInit()
|
|
|
6307
6668
|
return;
|
|
6308
6669
|
}
|
|
6309
6670
|
|
|
6310
|
-
// create the
|
|
6671
|
+
// create the WebGL canvas
|
|
6311
6672
|
const rootElement = mainCanvas.parentElement;
|
|
6312
6673
|
rootElement.appendChild(glCanvas);
|
|
6313
6674
|
|
|
6314
|
-
// setup
|
|
6675
|
+
// setup instanced rendering shader program
|
|
6315
6676
|
glShader = glCreateProgram(
|
|
6316
6677
|
'#version 300 es\n' + // specify GLSL ES version
|
|
6317
6678
|
'precision highp float;'+ // use highp for better accuracy
|
|
@@ -6339,40 +6700,59 @@ function glInit()
|
|
|
6339
6700
|
'}' // end of shader
|
|
6340
6701
|
);
|
|
6341
6702
|
|
|
6703
|
+
// setup poly rendering shaders
|
|
6704
|
+
glPolyShader = glCreateProgram(
|
|
6705
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
6706
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
6707
|
+
'uniform mat4 m;'+ // transform matrix
|
|
6708
|
+
'in vec2 p;'+ // in: position
|
|
6709
|
+
'in vec4 c;'+ // in: color
|
|
6710
|
+
'out vec4 d;'+ // out: color
|
|
6711
|
+
'void main(){'+ // shader entry point
|
|
6712
|
+
'gl_Position=m*vec4(p,1,1);'+ // transform position
|
|
6713
|
+
'd=c;'+ // pass color to fragment shader
|
|
6714
|
+
'}' // end of shader
|
|
6715
|
+
,
|
|
6716
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
6717
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
6718
|
+
'in vec4 d;'+ // in: color
|
|
6719
|
+
'out vec4 c;'+ // out: color
|
|
6720
|
+
'void main(){'+ // shader entry point
|
|
6721
|
+
'c=d;'+ // set color
|
|
6722
|
+
'}' // end of shader
|
|
6723
|
+
);
|
|
6724
|
+
|
|
6342
6725
|
// init buffers
|
|
6343
|
-
const glInstanceData = new ArrayBuffer(
|
|
6726
|
+
const glInstanceData = new ArrayBuffer(gl_ARRAY_BUFFER_SIZE);
|
|
6344
6727
|
glPositionData = new Float32Array(glInstanceData);
|
|
6345
6728
|
glColorData = new Uint32Array(glInstanceData);
|
|
6346
6729
|
glArrayBuffer = glContext.createBuffer();
|
|
6347
6730
|
glGeometryBuffer = glContext.createBuffer();
|
|
6348
6731
|
|
|
6349
6732
|
// create the geometry buffer, triangle strip square
|
|
6350
|
-
const geometry = new Float32Array([
|
|
6733
|
+
const geometry = new Float32Array([glBatchCount=0,0,1,0,0,1,1,1]);
|
|
6351
6734
|
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
6352
6735
|
glContext.bufferData(glContext.ARRAY_BUFFER, geometry, glContext.STATIC_DRAW);
|
|
6353
6736
|
}
|
|
6354
6737
|
|
|
6355
|
-
|
|
6356
|
-
// Also used by tile layer rendering when redrawing tiles
|
|
6357
|
-
function glPreRender()
|
|
6738
|
+
function glSetInstancedMode()
|
|
6358
6739
|
{
|
|
6359
|
-
if (!
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6740
|
+
if (!glPolyMode)
|
|
6741
|
+
return;
|
|
6742
|
+
|
|
6743
|
+
// setup instanced mode
|
|
6744
|
+
glFlush();
|
|
6745
|
+
glPolyMode = false;
|
|
6363
6746
|
glContext.useProgram(glShader);
|
|
6364
|
-
glContext.activeTexture(glContext.TEXTURE0);
|
|
6365
|
-
if (textureInfos[0])
|
|
6366
|
-
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
|
|
6367
6747
|
|
|
6368
6748
|
// set vertex attributes
|
|
6369
|
-
let offset =
|
|
6749
|
+
let offset = 0;
|
|
6370
6750
|
const initVertexAttribArray = (name, type, typeSize, size)=>
|
|
6371
6751
|
{
|
|
6372
6752
|
const location = glContext.getAttribLocation(glShader, name);
|
|
6373
6753
|
const stride = typeSize && gl_INSTANCE_BYTE_STRIDE; // only if not geometry
|
|
6374
6754
|
const divisor = typeSize && 1; // only if not geometry
|
|
6375
|
-
const normalize = typeSize
|
|
6755
|
+
const normalize = typeSize === 1; // only if color
|
|
6376
6756
|
glContext.enableVertexAttribArray(location);
|
|
6377
6757
|
glContext.vertexAttribPointer(location, size, type, normalize, stride, offset);
|
|
6378
6758
|
glContext.vertexAttribDivisor(location, divisor);
|
|
@@ -6381,26 +6761,87 @@ function glPreRender()
|
|
|
6381
6761
|
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
6382
6762
|
initVertexAttribArray('g', glContext.FLOAT, 0, 2); // geometry
|
|
6383
6763
|
glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
|
|
6384
|
-
glContext.bufferData(glContext.ARRAY_BUFFER,
|
|
6764
|
+
glContext.bufferData(glContext.ARRAY_BUFFER, gl_ARRAY_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
|
|
6385
6765
|
initVertexAttribArray('p', glContext.FLOAT, 4, 4); // position & size
|
|
6386
6766
|
initVertexAttribArray('u', glContext.FLOAT, 4, 4); // texture coords
|
|
6387
6767
|
initVertexAttribArray('c', glContext.UNSIGNED_BYTE, 1, 4); // color
|
|
6388
6768
|
initVertexAttribArray('a', glContext.UNSIGNED_BYTE, 1, 4); // additiveColor
|
|
6389
6769
|
initVertexAttribArray('r', glContext.FLOAT, 4, 1); // rotation
|
|
6770
|
+
}
|
|
6771
|
+
|
|
6772
|
+
function glSetPolyMode()
|
|
6773
|
+
{
|
|
6774
|
+
if (glPolyMode)
|
|
6775
|
+
return;
|
|
6390
6776
|
|
|
6777
|
+
// setup poly mode
|
|
6778
|
+
glFlush();
|
|
6779
|
+
glPolyMode = true;
|
|
6780
|
+
glContext.useProgram(glPolyShader);
|
|
6781
|
+
|
|
6782
|
+
// set vertex attributes
|
|
6783
|
+
let offset = 0;
|
|
6784
|
+
const initVertexAttribArray = (name, type, typeSize, size)=>
|
|
6785
|
+
{
|
|
6786
|
+
const location = glContext.getAttribLocation(glPolyShader, name);
|
|
6787
|
+
const normalize = typeSize === 1; // only normalize if color
|
|
6788
|
+
const stride = gl_POLY_VERTEX_BYTE_STRIDE;
|
|
6789
|
+
glContext.enableVertexAttribArray(location);
|
|
6790
|
+
glContext.vertexAttribPointer(location, size, type, normalize, stride, offset);
|
|
6791
|
+
glContext.vertexAttribDivisor(location, 0);
|
|
6792
|
+
offset += size*typeSize;
|
|
6793
|
+
}
|
|
6794
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
|
|
6795
|
+
glContext.bufferData(glContext.ARRAY_BUFFER, gl_ARRAY_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
|
|
6796
|
+
initVertexAttribArray('p', glContext.FLOAT, 4, 2); // position
|
|
6797
|
+
initVertexAttribArray('c', glContext.UNSIGNED_BYTE, 1, 4); // color
|
|
6798
|
+
}
|
|
6799
|
+
|
|
6800
|
+
// Setup WebGL render each frame, called automatically by engine
|
|
6801
|
+
// Also used by tile layer rendering when redrawing tiles
|
|
6802
|
+
function glPreRender()
|
|
6803
|
+
{
|
|
6804
|
+
if (!glEnable || !glContext) return;
|
|
6805
|
+
|
|
6806
|
+
// clear the canvas
|
|
6807
|
+
glClearCanvas();
|
|
6808
|
+
|
|
6391
6809
|
// build the transform matrix
|
|
6392
6810
|
const s = vec2(2*cameraScale).divide(mainCanvasSize);
|
|
6393
6811
|
const rotatedCam = cameraPos.rotate(-cameraAngle);
|
|
6394
6812
|
const p = vec2(-1).subtract(rotatedCam.multiply(s));
|
|
6395
6813
|
const ca = Math.cos(cameraAngle);
|
|
6396
6814
|
const sa = Math.sin(cameraAngle);
|
|
6397
|
-
|
|
6398
|
-
[
|
|
6815
|
+
const transform = [
|
|
6399
6816
|
s.x * ca, s.y * sa, 0, 0,
|
|
6400
6817
|
-s.x * sa, s.y * ca, 0, 0,
|
|
6401
6818
|
1, 1, 1, 0,
|
|
6402
|
-
p.x, p.y, 0, 1
|
|
6403
|
-
|
|
6819
|
+
p.x, p.y, 0, 1];
|
|
6820
|
+
|
|
6821
|
+
// set the same transform matrix for both shaders
|
|
6822
|
+
const initUniform = (program, uniform, value) =>
|
|
6823
|
+
{
|
|
6824
|
+
glContext.useProgram(program);
|
|
6825
|
+
const location = glContext.getUniformLocation(program, uniform);
|
|
6826
|
+
glContext.uniformMatrix4fv(location, false, value);
|
|
6827
|
+
}
|
|
6828
|
+
initUniform(glPolyShader, 'm', transform);
|
|
6829
|
+
initUniform(glShader, 'm', transform);
|
|
6830
|
+
|
|
6831
|
+
// set the active texture
|
|
6832
|
+
glContext.activeTexture(glContext.TEXTURE0);
|
|
6833
|
+
if (textureInfos[0])
|
|
6834
|
+
{
|
|
6835
|
+
glActiveTexture = textureInfos[0].glTexture;
|
|
6836
|
+
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
|
|
6837
|
+
}
|
|
6838
|
+
|
|
6839
|
+
// start with additive blending off
|
|
6840
|
+
glAdditive = glBatchAdditive = false;
|
|
6841
|
+
|
|
6842
|
+
// force it to enter instanced mode
|
|
6843
|
+
glPolyMode = true;
|
|
6844
|
+
glSetInstancedMode();
|
|
6404
6845
|
}
|
|
6405
6846
|
|
|
6406
6847
|
/** Clear the canvas and setup the viewport
|
|
@@ -6408,21 +6849,23 @@ function glPreRender()
|
|
|
6408
6849
|
function glClearCanvas()
|
|
6409
6850
|
{
|
|
6410
6851
|
if (!glContext) return;
|
|
6411
|
-
|
|
6852
|
+
|
|
6412
6853
|
// clear and set to same size as main canvas
|
|
6413
|
-
|
|
6854
|
+
glCanvas.width = drawCanvas.width;
|
|
6855
|
+
glCanvas.height = drawCanvas.height;
|
|
6856
|
+
glContext.viewport(0, 0, glCanvas.width, glCanvas.height);
|
|
6414
6857
|
glContext.clear(glContext.COLOR_BUFFER_BIT);
|
|
6415
6858
|
}
|
|
6416
6859
|
|
|
6417
|
-
/** Set the
|
|
6860
|
+
/** Set the WebGL texture, called automatically if using multiple textures
|
|
6418
6861
|
* - This may also flush the gl buffer resulting in more draw calls and worse performance
|
|
6419
6862
|
* @param {WebGLTexture} texture
|
|
6420
|
-
* @param {boolean} wrap - Should the texture wrap or clamp
|
|
6863
|
+
* @param {boolean} [wrap] - Should the texture wrap or clamp
|
|
6421
6864
|
* @memberof WebGL */
|
|
6422
6865
|
function glSetTexture(texture, wrap=false)
|
|
6423
6866
|
{
|
|
6424
6867
|
// must flush cache with the old texture to set a new one
|
|
6425
|
-
if (!glContext || texture
|
|
6868
|
+
if (!glContext || texture === glActiveTexture)
|
|
6426
6869
|
return;
|
|
6427
6870
|
|
|
6428
6871
|
glFlush();
|
|
@@ -6435,8 +6878,8 @@ function glSetTexture(texture, wrap=false)
|
|
|
6435
6878
|
}
|
|
6436
6879
|
|
|
6437
6880
|
/** Compile WebGL shader of the given type, will throw errors if in debug mode
|
|
6438
|
-
* @param {
|
|
6439
|
-
* @param {
|
|
6881
|
+
* @param {string} source
|
|
6882
|
+
* @param {number} type
|
|
6440
6883
|
* @return {WebGLShader}
|
|
6441
6884
|
* @memberof WebGL */
|
|
6442
6885
|
function glCompileShader(source, type)
|
|
@@ -6455,8 +6898,8 @@ function glCompileShader(source, type)
|
|
|
6455
6898
|
}
|
|
6456
6899
|
|
|
6457
6900
|
/** Create WebGL program with given shaders
|
|
6458
|
-
* @param {
|
|
6459
|
-
* @param {
|
|
6901
|
+
* @param {string} vsSource
|
|
6902
|
+
* @param {string} fsSource
|
|
6460
6903
|
* @return {WebGLProgram}
|
|
6461
6904
|
* @memberof WebGL */
|
|
6462
6905
|
function glCreateProgram(vsSource, fsSource)
|
|
@@ -6504,7 +6947,7 @@ function glCreateTexture(image)
|
|
|
6504
6947
|
const whitePixel = new Uint8Array([255, 255, 255, 255]);
|
|
6505
6948
|
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, 1, 1, 0, glContext.RGBA, glContext.UNSIGNED_BYTE, whitePixel);
|
|
6506
6949
|
}
|
|
6507
|
-
|
|
6950
|
+
|
|
6508
6951
|
// set texture filtering
|
|
6509
6952
|
const filter = tilesPixelated ? glContext.NEAREST : glContext.LINEAR;
|
|
6510
6953
|
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, filter);
|
|
@@ -6512,7 +6955,6 @@ function glCreateTexture(image)
|
|
|
6512
6955
|
return texture;
|
|
6513
6956
|
}
|
|
6514
6957
|
|
|
6515
|
-
|
|
6516
6958
|
/** Deletes a WebGL texture
|
|
6517
6959
|
* @param {WebGLTexture} [texture]
|
|
6518
6960
|
* @memberof WebGL */
|
|
@@ -6540,18 +6982,25 @@ function glSetTextureData(texture, image)
|
|
|
6540
6982
|
* @memberof WebGL */
|
|
6541
6983
|
function glFlush()
|
|
6542
6984
|
{
|
|
6543
|
-
if (
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6985
|
+
if (glEnable && glContext && glBatchCount)
|
|
6986
|
+
{
|
|
6987
|
+
// set bend mode
|
|
6988
|
+
const destBlend = glBatchAdditive ? glContext.ONE : glContext.ONE_MINUS_SRC_ALPHA;
|
|
6989
|
+
glContext.blendFuncSeparate(glContext.SRC_ALPHA, destBlend, glContext.ONE, destBlend);
|
|
6990
|
+
glContext.enable(glContext.BLEND);
|
|
6991
|
+
|
|
6992
|
+
const byteLength = glBatchCount *
|
|
6993
|
+
(glPolyMode ? gl_INDICES_PER_POLY_VERTEX : gl_INDICES_PER_INSTANCE);
|
|
6994
|
+
glContext.bufferSubData(glContext.ARRAY_BUFFER, 0, glPositionData, 0, byteLength);
|
|
6995
|
+
|
|
6996
|
+
// draw the batch
|
|
6997
|
+
if (glPolyMode)
|
|
6998
|
+
glContext.drawArrays(glContext.TRIANGLE_STRIP, 0, glBatchCount);
|
|
6999
|
+
else
|
|
7000
|
+
glContext.drawArraysInstanced(glContext.TRIANGLE_STRIP, 0, 4, glBatchCount);
|
|
7001
|
+
drawCount += glBatchCount;
|
|
7002
|
+
glBatchCount = 0;
|
|
7003
|
+
}
|
|
6555
7004
|
glBatchAdditive = glAdditive;
|
|
6556
7005
|
}
|
|
6557
7006
|
|
|
@@ -6567,7 +7016,8 @@ function glCopyToContext(context)
|
|
|
6567
7016
|
context.drawImage(glCanvas, 0, 0);
|
|
6568
7017
|
}
|
|
6569
7018
|
|
|
6570
|
-
/** Set anti-aliasing for
|
|
7019
|
+
/** Set anti-aliasing for WebGL canvas
|
|
7020
|
+
* Must be called before engineInit
|
|
6571
7021
|
* @param {boolean} [antialias]
|
|
6572
7022
|
* @memberof WebGL */
|
|
6573
7023
|
function glSetAntialias(antialias=true)
|
|
@@ -6577,25 +7027,27 @@ function glSetAntialias(antialias=true)
|
|
|
6577
7027
|
}
|
|
6578
7028
|
|
|
6579
7029
|
/** Add a sprite to the gl draw list, used by all gl draw functions
|
|
6580
|
-
* @param {
|
|
6581
|
-
* @param {
|
|
6582
|
-
* @param {
|
|
6583
|
-
* @param {
|
|
6584
|
-
* @param {
|
|
6585
|
-
* @param {
|
|
6586
|
-
* @param {
|
|
6587
|
-
* @param {
|
|
6588
|
-
* @param {
|
|
6589
|
-
* @param {
|
|
6590
|
-
* @param {
|
|
7030
|
+
* @param {number} x
|
|
7031
|
+
* @param {number} y
|
|
7032
|
+
* @param {number} sizeX
|
|
7033
|
+
* @param {number} sizeY
|
|
7034
|
+
* @param {number} [angle]
|
|
7035
|
+
* @param {number} [uv0X]
|
|
7036
|
+
* @param {number} [uv0Y]
|
|
7037
|
+
* @param {number} [uv1X]
|
|
7038
|
+
* @param {number} [uv1Y]
|
|
7039
|
+
* @param {number} [rgba=-1] - white is -1
|
|
7040
|
+
* @param {number} [rgbaAdditive=0] - black is 0
|
|
6591
7041
|
* @memberof WebGL */
|
|
6592
7042
|
function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgba=-1, rgbaAdditive=0)
|
|
6593
7043
|
{
|
|
6594
7044
|
// flush if there is not enough room or if different blend mode
|
|
6595
|
-
if (
|
|
7045
|
+
if (glBatchCount >= gl_MAX_INSTANCES || glBatchAdditive !== glAdditive)
|
|
6596
7046
|
glFlush();
|
|
7047
|
+
glSetInstancedMode();
|
|
6597
7048
|
|
|
6598
|
-
|
|
7049
|
+
glPolyMode = false;
|
|
7050
|
+
let offset = glBatchCount++ * gl_INDICES_PER_INSTANCE;
|
|
6599
7051
|
glPositionData[offset++] = x;
|
|
6600
7052
|
glPositionData[offset++] = y;
|
|
6601
7053
|
glPositionData[offset++] = sizeX;
|
|
@@ -6607,6 +7059,312 @@ function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgb
|
|
|
6607
7059
|
glColorData[offset++] = rgba;
|
|
6608
7060
|
glColorData[offset++] = rgbaAdditive;
|
|
6609
7061
|
glPositionData[offset++] = angle;
|
|
7062
|
+
}
|
|
7063
|
+
|
|
7064
|
+
/** Transform and add a polygon to the gl draw list
|
|
7065
|
+
* @param {Array<Vector2>} points - Array of Vector2 points
|
|
7066
|
+
* @param {number} rgba - Color of the polygon as a 32-bit integer
|
|
7067
|
+
* @param {number} x
|
|
7068
|
+
* @param {number} y
|
|
7069
|
+
* @param {number} sx
|
|
7070
|
+
* @param {number} sy
|
|
7071
|
+
* @param {number} angle
|
|
7072
|
+
* @param {boolean} [tristrip] - should tristrip algorithm be used
|
|
7073
|
+
* @memberof WebGL */
|
|
7074
|
+
function glDrawPointsTransform(points, rgba, x, y, sx, sy, angle, tristrip=true)
|
|
7075
|
+
{
|
|
7076
|
+
const pointsOut = [];
|
|
7077
|
+
for (const p of points)
|
|
7078
|
+
{
|
|
7079
|
+
// transform the point
|
|
7080
|
+
const px = p.x*sx;
|
|
7081
|
+
const py = p.y*sy;
|
|
7082
|
+
const sa = Math.sin(-angle);
|
|
7083
|
+
const ca = Math.cos(-angle);
|
|
7084
|
+
pointsOut.push(vec2(x + ca*px - sa*py, y + sa*px + ca*py));
|
|
7085
|
+
}
|
|
7086
|
+
const drawPoints = tristrip ? glPolyStrip(pointsOut) : pointsOut;
|
|
7087
|
+
glDrawPoints(drawPoints, rgba);
|
|
7088
|
+
}
|
|
7089
|
+
|
|
7090
|
+
/** Transform and add a polygon to the gl draw list
|
|
7091
|
+
* @param {Array<Vector2>} points - Array of Vector2 points
|
|
7092
|
+
* @param {number} rgba - Color of the polygon as a 32-bit integer
|
|
7093
|
+
* @param {number} lineWidth - Width of the outline
|
|
7094
|
+
* @param {number} x
|
|
7095
|
+
* @param {number} y
|
|
7096
|
+
* @param {number} sx
|
|
7097
|
+
* @param {number} sy
|
|
7098
|
+
* @param {number} angle
|
|
7099
|
+
* @param {boolean} [wrap] - Should the outline connect the first and last points
|
|
7100
|
+
* @memberof WebGL */
|
|
7101
|
+
function glDrawOutlineTransform(points, rgba, lineWidth, x, y, sx, sy, angle, wrap=true)
|
|
7102
|
+
{
|
|
7103
|
+
const outlinePoints = glMakeOutline(points, lineWidth, wrap);
|
|
7104
|
+
glDrawPointsTransform(outlinePoints, rgba, x, y, sx, sy, angle, false);
|
|
7105
|
+
}
|
|
7106
|
+
|
|
7107
|
+
/** Add a list of points to the gl draw list
|
|
7108
|
+
* @param {Array<Vector2>} points - Array of Vector2 points in tri strip order
|
|
7109
|
+
* @param {number} rgba - Color as a 32-bit integer
|
|
7110
|
+
* @memberof WebGL */
|
|
7111
|
+
function glDrawPoints(points, rgba)
|
|
7112
|
+
{
|
|
7113
|
+
if (!glEnable || points.length < 3)
|
|
7114
|
+
return; // needs at least 3 points to have area
|
|
7115
|
+
|
|
7116
|
+
// flush if there is not enough room or if different blend mode
|
|
7117
|
+
const vertCount = points.length + 2;
|
|
7118
|
+
if (glBatchCount+vertCount >= gl_MAX_POLY_VERTEXES || glBatchAdditive !== glAdditive)
|
|
7119
|
+
glFlush();
|
|
7120
|
+
glSetPolyMode();
|
|
7121
|
+
|
|
7122
|
+
// setup triangle strip with degenerate verts at start and end
|
|
7123
|
+
let offset = glBatchCount * gl_INDICES_PER_POLY_VERTEX;
|
|
7124
|
+
for(let i = vertCount; i--;)
|
|
7125
|
+
{
|
|
7126
|
+
const j = clamp(i-1, 0, vertCount-3);
|
|
7127
|
+
const point = points[j];
|
|
7128
|
+
glPositionData[offset++] = point.x;
|
|
7129
|
+
glPositionData[offset++] = point.y;
|
|
7130
|
+
glColorData[offset++] = rgba;
|
|
7131
|
+
}
|
|
7132
|
+
glBatchCount += vertCount;
|
|
7133
|
+
}
|
|
7134
|
+
|
|
7135
|
+
/** Add a list of colored points to the gl draw list
|
|
7136
|
+
* @param {Array<Vector2>} points - Array of Vector2 points in tri strip order
|
|
7137
|
+
* @param {Array<number>} pointColors - Array of 32-bit integer colors
|
|
7138
|
+
* @memberof WebGL */
|
|
7139
|
+
function glDrawColoredPoints(points, pointColors)
|
|
7140
|
+
{
|
|
7141
|
+
if (!glEnable || points.length < 3)
|
|
7142
|
+
return; // needs at least 3 points to have area
|
|
7143
|
+
|
|
7144
|
+
// flush if there is not enough room or if different blend mode
|
|
7145
|
+
const vertCount = points.length + 2;
|
|
7146
|
+
if (glBatchCount+vertCount >= gl_MAX_POLY_VERTEXES || glBatchAdditive !== glAdditive)
|
|
7147
|
+
glFlush();
|
|
7148
|
+
glSetPolyMode();
|
|
7149
|
+
|
|
7150
|
+
// setup triangle strip with degenerate verts at start and end
|
|
7151
|
+
let offset = glBatchCount * gl_INDICES_PER_POLY_VERTEX;
|
|
7152
|
+
for(let i = vertCount; i--;)
|
|
7153
|
+
{
|
|
7154
|
+
const j = clamp(i-1, 0, vertCount-3);
|
|
7155
|
+
const point = points[j];
|
|
7156
|
+
const color = pointColors[j];
|
|
7157
|
+
glPositionData[offset++] = point.x;
|
|
7158
|
+
glPositionData[offset++] = point.y;
|
|
7159
|
+
glColorData[offset++] = color;
|
|
7160
|
+
}
|
|
7161
|
+
glBatchCount += vertCount;
|
|
7162
|
+
}
|
|
7163
|
+
|
|
7164
|
+
// WebGL internal function to convert polygon to outline triangle strip
|
|
7165
|
+
function glMakeOutline(points, width, wrap=true)
|
|
7166
|
+
{
|
|
7167
|
+
if (points.length < 2)
|
|
7168
|
+
return [];
|
|
7169
|
+
|
|
7170
|
+
const halfWidth = width / 2;
|
|
7171
|
+
const strip = [];
|
|
7172
|
+
const n = points.length;
|
|
7173
|
+
const e = 1e-6;
|
|
7174
|
+
const miterLimit = width*100;
|
|
7175
|
+
for (let i = 0; i < n; i++)
|
|
7176
|
+
{
|
|
7177
|
+
// for each vertex, calculate normal based on adjacent edges
|
|
7178
|
+
const prev = points[wrap ? (i - 1 + n) % n : max(i - 1, 0)];
|
|
7179
|
+
const curr = points[i];
|
|
7180
|
+
const next = points[wrap ? (i + 1) % n : min(i + 1, n - 1)];
|
|
7181
|
+
|
|
7182
|
+
// direction from previous to current
|
|
7183
|
+
const dx1 = curr.x - prev.x;
|
|
7184
|
+
const dy1 = curr.y - prev.y;
|
|
7185
|
+
const len1 = (dx1*dx1 + dy1*dy1)**.5;
|
|
7186
|
+
|
|
7187
|
+
// direction from current to next
|
|
7188
|
+
const dx2 = next.x - curr.x;
|
|
7189
|
+
const dy2 = next.y - curr.y;
|
|
7190
|
+
const len2 = (dx2*dx2 + dy2*dy2)**.5;
|
|
7191
|
+
|
|
7192
|
+
if (len1 < e && len2 < e)
|
|
7193
|
+
continue; // skip degenerate point
|
|
7194
|
+
|
|
7195
|
+
// calculate perpendicular normals for each edge
|
|
7196
|
+
const nx1 = len1 > e ? -dy1 / len1 : 0;
|
|
7197
|
+
const ny1 = len1 > e ? dx1 / len1 : 0;
|
|
7198
|
+
const nx2 = len2 > e ? -dy2 / len2 : 0;
|
|
7199
|
+
const ny2 = len2 > e ? dx2 / len2 : 0;
|
|
7200
|
+
|
|
7201
|
+
// average the normals for miter
|
|
7202
|
+
let nx = nx1 + nx2;
|
|
7203
|
+
let ny = ny1 + ny2;
|
|
7204
|
+
const nlen = (nx*nx + ny*ny)**.5;
|
|
7205
|
+
if (nlen < e)
|
|
7206
|
+
{
|
|
7207
|
+
// 180 degree turn - use perpendicular
|
|
7208
|
+
nx = nx1;
|
|
7209
|
+
ny = ny1;
|
|
7210
|
+
}
|
|
7211
|
+
else
|
|
7212
|
+
{
|
|
7213
|
+
// calculate miter length
|
|
7214
|
+
nx /= nlen;
|
|
7215
|
+
ny /= nlen;
|
|
7216
|
+
const dot = nx1 * nx + ny1 * ny;
|
|
7217
|
+
if (dot > e)
|
|
7218
|
+
{
|
|
7219
|
+
// scale normal by miter length, clamped to miterLimit
|
|
7220
|
+
const miterLength = min(1 / dot, miterLimit);
|
|
7221
|
+
nx *= miterLength;
|
|
7222
|
+
ny *= miterLength;
|
|
7223
|
+
}
|
|
7224
|
+
}
|
|
7225
|
+
|
|
7226
|
+
// create inner and outer points along the normal
|
|
7227
|
+
const inner = vec2(curr.x - nx * halfWidth, curr.y - ny * halfWidth);
|
|
7228
|
+
const outer = vec2(curr.x + nx * halfWidth, curr.y + ny * halfWidth);
|
|
7229
|
+
strip.push(inner);
|
|
7230
|
+
strip.push(outer);
|
|
7231
|
+
}
|
|
7232
|
+
if (strip.length > 1 && wrap)
|
|
7233
|
+
{
|
|
7234
|
+
// close the loop
|
|
7235
|
+
strip.push(strip[0]);
|
|
7236
|
+
strip.push(strip[1]);
|
|
7237
|
+
}
|
|
7238
|
+
return strip;
|
|
7239
|
+
}
|
|
7240
|
+
|
|
7241
|
+
// WebGL internal function to convert polys to tri strips
|
|
7242
|
+
function glPolyStrip(points)
|
|
7243
|
+
{
|
|
7244
|
+
// validate input
|
|
7245
|
+
if (points.length < 3)
|
|
7246
|
+
return [];
|
|
7247
|
+
|
|
7248
|
+
// cross product helper: (b-a) x (c-a)
|
|
7249
|
+
const cross = (a,b,c)=> (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
|
|
7250
|
+
|
|
7251
|
+
// calculate signed area of polygon
|
|
7252
|
+
const signedArea = (poly)=>
|
|
7253
|
+
{
|
|
7254
|
+
let area = 0;
|
|
7255
|
+
for (let i = poly.length; i--;)
|
|
7256
|
+
{
|
|
7257
|
+
const j = (i+1) % poly.length;
|
|
7258
|
+
area += poly[i].cross(poly[j]);
|
|
7259
|
+
}
|
|
7260
|
+
return area;
|
|
7261
|
+
}
|
|
7262
|
+
|
|
7263
|
+
// ensure counter-clockwise winding
|
|
7264
|
+
if (signedArea(points) < 0)
|
|
7265
|
+
points = points.reverse();
|
|
7266
|
+
|
|
7267
|
+
// check if point is inside triangle
|
|
7268
|
+
const e = 1e-9;
|
|
7269
|
+
const pointInTriangle = (p, a, b, c)=>
|
|
7270
|
+
{
|
|
7271
|
+
const c1 = cross(a, b, p);
|
|
7272
|
+
const c2 = cross(b, c, p);
|
|
7273
|
+
const c3 = cross(c, a, p);
|
|
7274
|
+
const negative = (c1<-e?1:0) + (c2<-e?1:0) + (c3<-e?1:0);
|
|
7275
|
+
const positive = (c1> e?1:0) + (c2> e?1:0) + (c3> e?1:0);
|
|
7276
|
+
return !(negative && positive);
|
|
7277
|
+
};
|
|
7278
|
+
|
|
7279
|
+
// ear clipping triangulation
|
|
7280
|
+
const indices = [];
|
|
7281
|
+
for (let i = 0; i < points.length; ++i)
|
|
7282
|
+
indices[i] = i;
|
|
7283
|
+
const triangles = [];
|
|
7284
|
+
let attempts = 0;
|
|
7285
|
+
const maxAttempts = points.length ** 2 + 100;
|
|
7286
|
+
while (indices.length > 3 && attempts++ < maxAttempts)
|
|
7287
|
+
{
|
|
7288
|
+
let foundEar = false;
|
|
7289
|
+
for (let i = indices.length; --i;)
|
|
7290
|
+
{
|
|
7291
|
+
const i0 = indices[(i + indices.length - 1) % indices.length];
|
|
7292
|
+
const i1 = indices[i];
|
|
7293
|
+
const i2 = indices[(i + 1) % indices.length];
|
|
7294
|
+
const a = points[i0], b = points[i1], c = points[i2];
|
|
7295
|
+
|
|
7296
|
+
// check if convex
|
|
7297
|
+
if (cross(a, b, c) < e)
|
|
7298
|
+
continue;
|
|
7299
|
+
|
|
7300
|
+
// check if any other point is inside
|
|
7301
|
+
let hasInside = false;
|
|
7302
|
+
for (let j = 0; j < indices.length; j++)
|
|
7303
|
+
{
|
|
7304
|
+
const k = indices[j];
|
|
7305
|
+
if (k === i0 || k === i1 || k === i2)
|
|
7306
|
+
continue;
|
|
7307
|
+
const p = points[k];
|
|
7308
|
+
hasInside = pointInTriangle(p, a, b, c);
|
|
7309
|
+
if (hasInside)
|
|
7310
|
+
break;
|
|
7311
|
+
}
|
|
7312
|
+
if (hasInside)
|
|
7313
|
+
continue;
|
|
7314
|
+
|
|
7315
|
+
// found valid ear
|
|
7316
|
+
triangles.push([i0, i1, i2]);
|
|
7317
|
+
indices.splice(i, 1);
|
|
7318
|
+
foundEar = true;
|
|
7319
|
+
break;
|
|
7320
|
+
}
|
|
7321
|
+
|
|
7322
|
+
// fallback for degenerate cases
|
|
7323
|
+
if (!foundEar)
|
|
7324
|
+
{
|
|
7325
|
+
let worstIndex = -1, worstValue = Infinity;
|
|
7326
|
+
for (let i = indices.length; --i;)
|
|
7327
|
+
{
|
|
7328
|
+
const i0 = indices[(i + indices.length - 1) % indices.length];
|
|
7329
|
+
const i1 = indices[i];
|
|
7330
|
+
const i2 = indices[(i + 1) % indices.length];
|
|
7331
|
+
const value = abs(cross(points[i0], points[i1], points[i2]));
|
|
7332
|
+
if (value < worstValue)
|
|
7333
|
+
{
|
|
7334
|
+
worstValue = value;
|
|
7335
|
+
worstIndex = i;
|
|
7336
|
+
}
|
|
7337
|
+
}
|
|
7338
|
+
if (worstIndex < 0)
|
|
7339
|
+
break;
|
|
7340
|
+
|
|
7341
|
+
const i0 = indices[(worstIndex + indices.length - 1) % indices.length];
|
|
7342
|
+
const i1 = indices[worstIndex];
|
|
7343
|
+
const i2 = indices[(worstIndex + 1) % indices.length];
|
|
7344
|
+
triangles.push([i0, i1, i2]);
|
|
7345
|
+
indices.splice(worstIndex, 1);
|
|
7346
|
+
}
|
|
7347
|
+
}
|
|
7348
|
+
|
|
7349
|
+
// add final triangle
|
|
7350
|
+
if (indices.length === 3)
|
|
7351
|
+
triangles.push([indices[0], indices[1], indices[2]]);
|
|
7352
|
+
if (!triangles.length)
|
|
7353
|
+
return [];
|
|
7354
|
+
|
|
7355
|
+
// convert triangles to triangle strip with degenerate connectors
|
|
7356
|
+
const strip = [];
|
|
7357
|
+
let [a0, b0, c0] = triangles[0];
|
|
7358
|
+
strip.push(points[a0], points[b0], points[c0]);
|
|
7359
|
+
for (let i = 1; i < triangles.length; i++)
|
|
7360
|
+
{
|
|
7361
|
+
// add degenerate bridge from last vertex to first of new triangle
|
|
7362
|
+
const [a, b, c] = triangles[i];
|
|
7363
|
+
strip.push(points[c0], points[a]);
|
|
7364
|
+
strip.push(points[a], points[b], points[c]);
|
|
7365
|
+
c0 = c;
|
|
7366
|
+
}
|
|
7367
|
+
return strip;
|
|
6610
7368
|
}
|
|
6611
7369
|
/**
|
|
6612
7370
|
* LittleJS Newgrounds API
|
|
@@ -6631,11 +7389,11 @@ let newgrounds;
|
|
|
6631
7389
|
class NewgroundsMedal extends Medal
|
|
6632
7390
|
{
|
|
6633
7391
|
/** Create a newgrounds medal object and adds it to the list of medals
|
|
6634
|
-
* @param {
|
|
6635
|
-
* @param {
|
|
6636
|
-
* @param {
|
|
6637
|
-
* @param {
|
|
6638
|
-
* @param {
|
|
7392
|
+
* @param {number} id - The unique identifier of the medal
|
|
7393
|
+
* @param {string} name - Name of the medal
|
|
7394
|
+
* @param {string} [description] - Description of the medal
|
|
7395
|
+
* @param {string} [icon] - Icon for the medal
|
|
7396
|
+
* @param {string} [src] - Image location for the medal
|
|
6639
7397
|
*/
|
|
6640
7398
|
constructor(id, name, description, icon, src)
|
|
6641
7399
|
{ super(id, name, description, icon, src); }
|
|
@@ -6727,7 +7485,7 @@ class NewgroundsPlugin
|
|
|
6727
7485
|
* @param {number} id - The scoreboard id
|
|
6728
7486
|
* @param {string} [user] - A user's id or name
|
|
6729
7487
|
* @param {number} [social] - If true, only social scores will be loaded
|
|
6730
|
-
* @param {number} [skip] - Number of scores to skip
|
|
7488
|
+
* @param {number} [skip] - Number of scores to skip over
|
|
6731
7489
|
* @param {number} [limit] - Number of scores to include in the list
|
|
6732
7490
|
* @return {Object} - The response JSON object
|
|
6733
7491
|
*/
|
|
@@ -6950,16 +7708,16 @@ class ZzFXMusic extends Sound
|
|
|
6950
7708
|
if (!soundEnable || headlessMode) return;
|
|
6951
7709
|
this.randomness = 0;
|
|
6952
7710
|
this.sampleChannels = zzfxM(...zzfxMusic);
|
|
6953
|
-
this.sampleRate =
|
|
7711
|
+
this.sampleRate = audioDefaultSampleRate;
|
|
6954
7712
|
}
|
|
6955
7713
|
|
|
6956
|
-
/** Play the music
|
|
6957
|
-
* @param {number} [volume
|
|
6958
|
-
* @param {boolean} [loop] -
|
|
7714
|
+
/** Play the music that loops by default
|
|
7715
|
+
* @param {number} [volume] - Volume to play the music at
|
|
7716
|
+
* @param {boolean} [loop] - Should the music loop?
|
|
6959
7717
|
* @return {AudioBufferSourceNode} - The audio source node
|
|
6960
7718
|
*/
|
|
6961
|
-
playMusic(volume, loop=
|
|
6962
|
-
{ return super.play(undefined, volume, 1,
|
|
7719
|
+
playMusic(volume=1, loop=true)
|
|
7720
|
+
{ return super.play(undefined, volume, 1, 0, loop); }
|
|
6963
7721
|
}
|
|
6964
7722
|
|
|
6965
7723
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -6993,7 +7751,7 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
6993
7751
|
let panning = 0;
|
|
6994
7752
|
let hasMore = 1;
|
|
6995
7753
|
let sampleCache = {};
|
|
6996
|
-
let beatLength =
|
|
7754
|
+
let beatLength = audioDefaultSampleRate / BPM * 60 >> 2;
|
|
6997
7755
|
|
|
6998
7756
|
// for each channel in order until there are no more
|
|
6999
7757
|
for (; hasMore; channelIndex++) {
|
|
@@ -7012,15 +7770,15 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
7012
7770
|
// get next offset, use the length of first channel
|
|
7013
7771
|
nextSampleOffset = outSampleOffset + (patterns[patternIndex][0].length - 2 - (notFirstBeat?0:1)) * beatLength;
|
|
7014
7772
|
// for each beat in pattern, plus one extra if end of sequence
|
|
7015
|
-
isSequenceEnd = sequenceIndex
|
|
7773
|
+
isSequenceEnd = sequenceIndex === sequence.length - 1;
|
|
7016
7774
|
for (i = 2, k = outSampleOffset; i < patternChannel.length + isSequenceEnd; notFirstBeat = ++i) {
|
|
7017
7775
|
|
|
7018
7776
|
// <channel-note>
|
|
7019
7777
|
note = patternChannel[i];
|
|
7020
7778
|
|
|
7021
7779
|
// stop if end, different instrument or new note
|
|
7022
|
-
stop = i
|
|
7023
|
-
instrument
|
|
7780
|
+
stop = i === patternChannel.length + isSequenceEnd - 1 && isSequenceEnd ||
|
|
7781
|
+
instrument !== (patternChannel[0] || 0) || note | 0;
|
|
7024
7782
|
|
|
7025
7783
|
// fill buffer with samples for previous beat, most cpu intensive part
|
|
7026
7784
|
for (j = 0; j < beatLength && notFirstBeat;
|
|
@@ -7105,11 +7863,11 @@ class UISystemPlugin
|
|
|
7105
7863
|
/** @property {Color} - Default text color for UI elements */
|
|
7106
7864
|
this.defaultTextColor = BLACK;
|
|
7107
7865
|
/** @property {Color} - Default button color for UI elements */
|
|
7108
|
-
this.defaultButtonColor = hsl(0,0,.
|
|
7866
|
+
this.defaultButtonColor = hsl(0,0,.7);
|
|
7109
7867
|
/** @property {Color} - Default hover color for UI elements */
|
|
7110
|
-
this.defaultHoverColor = hsl(0,0,.
|
|
7868
|
+
this.defaultHoverColor = hsl(0,0,.9);
|
|
7111
7869
|
/** @property {Color} - Default color for disabled UI elements */
|
|
7112
|
-
this.defaultDisabledColor = hsl(0,0,.
|
|
7870
|
+
this.defaultDisabledColor = hsl(0,0,.3);
|
|
7113
7871
|
/** @property {number} - Default line width for UI elements */
|
|
7114
7872
|
this.defaultLineWidth = 4;
|
|
7115
7873
|
/** @property {number} - Default rounded rect corner radius for UI elements */
|
|
@@ -7129,16 +7887,16 @@ class UISystemPlugin
|
|
|
7129
7887
|
|
|
7130
7888
|
engineAddPlugin(uiUpdate, uiRender);
|
|
7131
7889
|
|
|
7132
|
-
function updateInvisible(o)
|
|
7133
|
-
{
|
|
7134
|
-
for (const c of o.children)
|
|
7135
|
-
updateInvisible(c);
|
|
7136
|
-
o.updateInvisible();
|
|
7137
|
-
}
|
|
7138
|
-
|
|
7139
7890
|
// setup recursive update and render
|
|
7140
7891
|
function uiUpdate()
|
|
7141
7892
|
{
|
|
7893
|
+
function updateInvisibleObject(o)
|
|
7894
|
+
{
|
|
7895
|
+
// update invisible objects
|
|
7896
|
+
for (const c of o.children)
|
|
7897
|
+
updateInvisibleObject(c);
|
|
7898
|
+
o.updateInvisible();
|
|
7899
|
+
}
|
|
7142
7900
|
function updateObject(o)
|
|
7143
7901
|
{
|
|
7144
7902
|
if (o.visible)
|
|
@@ -7152,7 +7910,7 @@ class UISystemPlugin
|
|
|
7152
7910
|
o.update();
|
|
7153
7911
|
}
|
|
7154
7912
|
else
|
|
7155
|
-
|
|
7913
|
+
updateInvisibleObject(o);
|
|
7156
7914
|
}
|
|
7157
7915
|
uiSystem.uiObjects.forEach(o=> o.parent || updateObject(o));
|
|
7158
7916
|
}
|
|
@@ -7178,7 +7936,7 @@ class UISystemPlugin
|
|
|
7178
7936
|
* @param {Color} [color=uiSystem.defaultColor]
|
|
7179
7937
|
* @param {number} [lineWidth=uiSystem.defaultLineWidth]
|
|
7180
7938
|
* @param {Color} [lineColor=uiSystem.defaultLineColor]
|
|
7181
|
-
* @param {number} [
|
|
7939
|
+
* @param {number} [cornerRadius=uiSystem.defaultCornerRadius] */
|
|
7182
7940
|
drawRect(pos, size, color=uiSystem.defaultColor, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor, cornerRadius=uiSystem.defaultCornerRadius)
|
|
7183
7941
|
{
|
|
7184
7942
|
const context = uiSystem.uiContext;
|
|
@@ -7253,32 +8011,40 @@ class UIObject
|
|
|
7253
8011
|
constructor(pos=vec2(), size=vec2())
|
|
7254
8012
|
{
|
|
7255
8013
|
/** @property {Vector2} - Local position of the object */
|
|
7256
|
-
this.localPos
|
|
8014
|
+
this.localPos = pos.copy();
|
|
7257
8015
|
/** @property {Vector2} - Screen space position of the object */
|
|
7258
|
-
this.pos
|
|
8016
|
+
this.pos = pos.copy();
|
|
7259
8017
|
/** @property {Vector2} - Screen space size of the object */
|
|
7260
|
-
this.size
|
|
7261
|
-
/** @property {Color} -
|
|
7262
|
-
this.color
|
|
7263
|
-
/** @property {
|
|
7264
|
-
this.
|
|
7265
|
-
/** @property {Color} -
|
|
8018
|
+
this.size = size.copy();
|
|
8019
|
+
/** @property {Color} - Color of the object */
|
|
8020
|
+
this.color = uiSystem.defaultColor;
|
|
8021
|
+
/** @property {string} - Text for this ui object */
|
|
8022
|
+
this.text = undefined;
|
|
8023
|
+
/** @property {Color} - Color when disabled */
|
|
8024
|
+
this.disabledColor = uiSystem.defaultDisabledColor;
|
|
8025
|
+
/** @property {boolean} - Is this object disabled? */
|
|
8026
|
+
this.disabled = false;
|
|
8027
|
+
/** @property {Color} - Color for text */
|
|
8028
|
+
this.textColor = uiSystem.defaultTextColor;
|
|
8029
|
+
/** @property {Color} - Color used when hovering over the object */
|
|
7266
8030
|
this.hoverColor = uiSystem.defaultHoverColor;
|
|
7267
|
-
/** @property {Color} -
|
|
7268
|
-
this.lineColor
|
|
7269
|
-
/** @property {number} -
|
|
7270
|
-
this.lineWidth
|
|
7271
|
-
/** @property {
|
|
7272
|
-
this.
|
|
7273
|
-
/** @property {
|
|
7274
|
-
this.
|
|
7275
|
-
/** @property {
|
|
7276
|
-
this.
|
|
7277
|
-
/** @property {
|
|
7278
|
-
this.
|
|
7279
|
-
/** @property {UIObject} - this object's
|
|
7280
|
-
this.
|
|
7281
|
-
/** @property {
|
|
8031
|
+
/** @property {Color} - Color for line drawing */
|
|
8032
|
+
this.lineColor = uiSystem.defaultLineColor;
|
|
8033
|
+
/** @property {number} - Width for line drawing */
|
|
8034
|
+
this.lineWidth = uiSystem.defaultLineWidth;
|
|
8035
|
+
/** @property {number} - Corner radius for rounded rects */
|
|
8036
|
+
this.cornerRadius = uiSystem.defaultCornerRadius;
|
|
8037
|
+
/** @property {string} - Font for this objecct */
|
|
8038
|
+
this.font = uiSystem.defaultFont;
|
|
8039
|
+
/** @property {number} - Override for text height */
|
|
8040
|
+
this.textHeight = undefined;
|
|
8041
|
+
/** @property {boolean} - Should this object be drawn */
|
|
8042
|
+
this.visible = true;
|
|
8043
|
+
/** @property {Array<UIObject>} - A list of this object's children */
|
|
8044
|
+
this.children = [];
|
|
8045
|
+
/** @property {UIObject} - This object's parent, position is in parent space */
|
|
8046
|
+
this.parent = undefined;
|
|
8047
|
+
/** @property {number} - Extra size added to make small buttons easier to touch on mobile devices */
|
|
7282
8048
|
this.extraTouchSize = 0;
|
|
7283
8049
|
/** @property {Sound} - Sound when interactive element is pressed */
|
|
7284
8050
|
this.soundPress = uiSystem.defaultSoundPress;
|
|
@@ -7310,7 +8076,7 @@ class UIObject
|
|
|
7310
8076
|
*/
|
|
7311
8077
|
removeChild(child)
|
|
7312
8078
|
{
|
|
7313
|
-
ASSERT(child.parent
|
|
8079
|
+
ASSERT(child.parent === this && this.children.includes(child));
|
|
7314
8080
|
this.children.splice(this.children.indexOf(child), 1);
|
|
7315
8081
|
child.parent = undefined;
|
|
7316
8082
|
}
|
|
@@ -7363,7 +8129,7 @@ class UIObject
|
|
|
7363
8129
|
this.mouseIsHeld = false;
|
|
7364
8130
|
}
|
|
7365
8131
|
|
|
7366
|
-
if (this.mouseIsOver
|
|
8132
|
+
if (this.mouseIsOver !== mouseWasOver)
|
|
7367
8133
|
this.mouseIsOver ? this.onEnter() : this.onLeave();
|
|
7368
8134
|
}
|
|
7369
8135
|
|
|
@@ -7374,7 +8140,7 @@ class UIObject
|
|
|
7374
8140
|
uiSystem.drawRect(this.pos, this.size, this.color, this.lineWidth, this.lineColor, this.cornerRadius);
|
|
7375
8141
|
}
|
|
7376
8142
|
|
|
7377
|
-
/** Special update
|
|
8143
|
+
/** Special update when object is not visible */
|
|
7378
8144
|
updateInvisible()
|
|
7379
8145
|
{
|
|
7380
8146
|
// reset input state when not visible
|
|
@@ -7382,28 +8148,22 @@ class UIObject
|
|
|
7382
8148
|
}
|
|
7383
8149
|
|
|
7384
8150
|
/** Called when the mouse enters the object */
|
|
7385
|
-
onEnter()
|
|
7386
|
-
{}
|
|
8151
|
+
onEnter() {}
|
|
7387
8152
|
|
|
7388
8153
|
/** Called when the mouse leaves the object */
|
|
7389
|
-
onLeave()
|
|
7390
|
-
{}
|
|
8154
|
+
onLeave() {}
|
|
7391
8155
|
|
|
7392
8156
|
/** Called when the mouse is pressed while over the object */
|
|
7393
|
-
onPress()
|
|
7394
|
-
{}
|
|
8157
|
+
onPress() {}
|
|
7395
8158
|
|
|
7396
8159
|
/** Called when the mouse is released while over the object */
|
|
7397
|
-
onRelease()
|
|
7398
|
-
{}
|
|
8160
|
+
onRelease() {}
|
|
7399
8161
|
|
|
7400
8162
|
/** Called when user clicks on this object */
|
|
7401
|
-
onClick()
|
|
7402
|
-
{}
|
|
8163
|
+
onClick() {}
|
|
7403
8164
|
|
|
7404
8165
|
/** Called when the state of this object changes */
|
|
7405
|
-
onChange()
|
|
7406
|
-
{}
|
|
8166
|
+
onChange() {}
|
|
7407
8167
|
};
|
|
7408
8168
|
|
|
7409
8169
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -7424,13 +8184,13 @@ class UIText extends UIObject
|
|
|
7424
8184
|
{
|
|
7425
8185
|
super(pos, size);
|
|
7426
8186
|
|
|
7427
|
-
|
|
8187
|
+
// set properties
|
|
7428
8188
|
this.text = text;
|
|
7429
|
-
/** @property {string} */
|
|
7430
8189
|
this.align = align;
|
|
8190
|
+
this.font = font;
|
|
7431
8191
|
|
|
7432
|
-
|
|
7433
|
-
this.lineWidth = 0;
|
|
8192
|
+
// make text not outlined by default
|
|
8193
|
+
this.lineWidth = 0;
|
|
7434
8194
|
}
|
|
7435
8195
|
render()
|
|
7436
8196
|
{
|
|
@@ -7457,13 +8217,14 @@ class UITile extends UIObject
|
|
|
7457
8217
|
constructor(pos, size, tileInfo, color=WHITE, angle=0, mirror=false)
|
|
7458
8218
|
{
|
|
7459
8219
|
super(pos, size);
|
|
7460
|
-
|
|
7461
8220
|
/** @property {TileInfo} - Tile image to use */
|
|
7462
8221
|
this.tileInfo = tileInfo;
|
|
7463
8222
|
/** @property {number} - Angle to rotate in radians */
|
|
7464
8223
|
this.angle = angle;
|
|
7465
8224
|
/** @property {boolean} - Should it be mirrored? */
|
|
7466
8225
|
this.mirror = mirror;
|
|
8226
|
+
|
|
8227
|
+
// set properties
|
|
7467
8228
|
this.color = color;
|
|
7468
8229
|
}
|
|
7469
8230
|
render()
|
|
@@ -7489,22 +8250,20 @@ class UIButton extends UIObject
|
|
|
7489
8250
|
{
|
|
7490
8251
|
super(pos, size);
|
|
7491
8252
|
|
|
7492
|
-
|
|
8253
|
+
// set properties
|
|
7493
8254
|
this.text = text;
|
|
7494
|
-
/** @property {Color} */
|
|
7495
|
-
this.disabledColor = uiSystem.defaultDisabledColor;
|
|
7496
|
-
/** @property {boolean} */
|
|
7497
|
-
this.disabled = false;
|
|
7498
|
-
this.interactive = true;
|
|
7499
8255
|
this.color = color;
|
|
8256
|
+
this.interactive = true;
|
|
7500
8257
|
}
|
|
7501
8258
|
render()
|
|
7502
8259
|
{
|
|
8260
|
+
// draw the button
|
|
7503
8261
|
const lineColor = this.mouseIsHeld && !this.disabled ? this.color : this.lineColor;
|
|
7504
8262
|
const color = this.disabled ? this.disabledColor : this.mouseIsOver ? this.hoverColor : this.color;
|
|
7505
8263
|
uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius);
|
|
7506
8264
|
|
|
7507
|
-
|
|
8265
|
+
// draw the text
|
|
8266
|
+
const textScale = .8; // scale text to fit
|
|
7508
8267
|
const textSize = vec2(this.size.x, this.textHeight || this.size.y*textScale);
|
|
7509
8268
|
uiSystem.drawText(this.text, this.pos, textSize,
|
|
7510
8269
|
this.textColor, 0, undefined, this.align, this.font);
|
|
@@ -7522,13 +8281,18 @@ class UICheckbox extends UIObject
|
|
|
7522
8281
|
* @param {Vector2} [pos]
|
|
7523
8282
|
* @param {Vector2} [size]
|
|
7524
8283
|
* @param {boolean} [checked]
|
|
8284
|
+
* @param {string} [text]
|
|
8285
|
+
* @param {Color} [color=uiSystem.defaultButtonColor]
|
|
7525
8286
|
*/
|
|
7526
|
-
constructor(pos, size, checked=false)
|
|
8287
|
+
constructor(pos, size, checked=false, text='', color=uiSystem.defaultButtonColor)
|
|
7527
8288
|
{
|
|
7528
8289
|
super(pos, size);
|
|
7529
|
-
|
|
7530
|
-
/** @property {boolean} */
|
|
8290
|
+
/** @property {boolean} - Current percentage value of this scrollbar 0-1 */
|
|
7531
8291
|
this.checked = checked;
|
|
8292
|
+
|
|
8293
|
+
// set properties
|
|
8294
|
+
this.text = text;
|
|
8295
|
+
this.color = color;
|
|
7532
8296
|
this.interactive = true;
|
|
7533
8297
|
}
|
|
7534
8298
|
onClick()
|
|
@@ -7538,14 +8302,24 @@ class UICheckbox extends UIObject
|
|
|
7538
8302
|
}
|
|
7539
8303
|
render()
|
|
7540
8304
|
{
|
|
7541
|
-
const color = this.mouseIsOver? this.hoverColor : this.color;
|
|
8305
|
+
const color = this.disabled ? this.disabledColor : this.mouseIsOver ? this.hoverColor : this.color;
|
|
7542
8306
|
uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, this.lineColor, this.cornerRadius);
|
|
7543
8307
|
if (this.checked)
|
|
7544
8308
|
{
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
|
|
8309
|
+
const p = this.cornerRadius / min(this.size.x, this.size.y) * 2;
|
|
8310
|
+
const length = lerp(1, 2**.5/2, p) / 2;
|
|
8311
|
+
let s = this.size.scale(length);
|
|
8312
|
+
uiSystem.drawLine(this.pos.add(s.multiply(vec2(-1))), this.pos.add(s.multiply(vec2(1))), this.lineWidth, this.lineColor);
|
|
8313
|
+
uiSystem.drawLine(this.pos.add(s.multiply(vec2(-1,1))), this.pos.add(s.multiply(vec2(1,-1))), this.lineWidth, this.lineColor);
|
|
7548
8314
|
}
|
|
8315
|
+
|
|
8316
|
+
// draw the text to the right side of the checkbox
|
|
8317
|
+
const textScale = .8; // scale text to fit
|
|
8318
|
+
const gapScale = .55;
|
|
8319
|
+
const textSize = vec2(this.size.x, this.textHeight || this.size.y*textScale);
|
|
8320
|
+
const pos = this.pos.add(vec2(this.size.x*gapScale,0));
|
|
8321
|
+
uiSystem.drawText(this.text, pos, textSize,
|
|
8322
|
+
this.textColor, 0, undefined, 'left', this.font);
|
|
7549
8323
|
}
|
|
7550
8324
|
}
|
|
7551
8325
|
|
|
@@ -7568,43 +8342,51 @@ class UIScrollbar extends UIObject
|
|
|
7568
8342
|
{
|
|
7569
8343
|
super(pos, size);
|
|
7570
8344
|
|
|
7571
|
-
/** @property {number} */
|
|
8345
|
+
/** @property {number} - Current percentage value of this scrollbar 0-1 */
|
|
7572
8346
|
this.value = value;
|
|
7573
|
-
/** @property {
|
|
7574
|
-
this.text = text;
|
|
7575
|
-
/** @property {Color} */
|
|
8347
|
+
/** @property {Color} - Color for the handle part of the scrollbar */
|
|
7576
8348
|
this.handleColor = handleColor;
|
|
8349
|
+
|
|
8350
|
+
// set properties
|
|
8351
|
+
this.text = text;
|
|
7577
8352
|
this.color = color;
|
|
7578
8353
|
this.interactive = true;
|
|
7579
8354
|
}
|
|
7580
8355
|
update()
|
|
7581
8356
|
{
|
|
7582
8357
|
super.update();
|
|
7583
|
-
if (this.mouseIsHeld)
|
|
8358
|
+
if (this.mouseIsHeld && this.interactive)
|
|
7584
8359
|
{
|
|
8360
|
+
// check if value changed
|
|
7585
8361
|
const handleSize = vec2(this.size.y);
|
|
7586
8362
|
const handleWidth = this.size.x - handleSize.x;
|
|
7587
8363
|
const p1 = this.pos.x - handleWidth/2;
|
|
7588
8364
|
const p2 = this.pos.x + handleWidth/2;
|
|
7589
8365
|
const oldValue = this.value;
|
|
7590
8366
|
this.value = percent(mousePosScreen.x, p1, p2);
|
|
7591
|
-
this.value
|
|
8367
|
+
this.value === oldValue || this.onChange();
|
|
7592
8368
|
}
|
|
7593
8369
|
}
|
|
7594
8370
|
render()
|
|
7595
8371
|
{
|
|
7596
|
-
|
|
7597
|
-
const
|
|
8372
|
+
// draw the scrollbar background
|
|
8373
|
+
const lineColor = this.interactive && this.mouseIsHeld && !this.disabled ?
|
|
8374
|
+
this.color : this.lineColor;
|
|
8375
|
+
const color = this.disabled ? this.disabledColor :
|
|
8376
|
+
this.interactive && this.mouseIsHeld ? this.hoverColor : this.color;
|
|
7598
8377
|
uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius);
|
|
7599
8378
|
|
|
8379
|
+
// draw the scrollbar handle
|
|
7600
8380
|
const handleSize = vec2(this.size.y);
|
|
7601
8381
|
const handleWidth = this.size.x - handleSize.x;
|
|
7602
8382
|
const p1 = this.pos.x - handleWidth/2;
|
|
7603
8383
|
const p2 = this.pos.x + handleWidth/2;
|
|
7604
8384
|
const handlePos = vec2(lerp(p1, p2, this.value), this.pos.y);
|
|
7605
|
-
const
|
|
7606
|
-
|
|
8385
|
+
const handleColor = this.disabled ? this.disabledColor :
|
|
8386
|
+
this.interactive && this.mouseIsHeld ? this.color : this.handleColor;
|
|
8387
|
+
uiSystem.drawRect(handlePos, handleSize, handleColor, this.lineWidth, this.lineColor, this.cornerRadius);
|
|
7607
8388
|
|
|
8389
|
+
// draw the text on the scrollbar
|
|
7608
8390
|
const textScale = .8; // scale text to fit in scrollbar
|
|
7609
8391
|
const textSize = vec2(this.size.x, this.textHeight || this.size.y*textScale);
|
|
7610
8392
|
uiSystem.drawText(this.text, this.pos, textSize,
|
|
@@ -7699,14 +8481,14 @@ class Box2dObject extends EngineObject
|
|
|
7699
8481
|
if (this.tileInfo)
|
|
7700
8482
|
super.render();
|
|
7701
8483
|
else
|
|
7702
|
-
this.drawFixtures(this.color, this.lineColor, this.lineWidth
|
|
8484
|
+
this.drawFixtures(this.color, this.lineColor, this.lineWidth);
|
|
7703
8485
|
}
|
|
7704
8486
|
|
|
7705
8487
|
/** Render debug info */
|
|
7706
8488
|
renderDebugInfo()
|
|
7707
8489
|
{
|
|
7708
8490
|
const isAsleep = !this.getIsAwake();
|
|
7709
|
-
const isStatic = this.getBodyType()
|
|
8491
|
+
const isStatic = this.getBodyType() === box2d.bodyTypeStatic;
|
|
7710
8492
|
const color = rgb(isAsleep?1:0, isAsleep?1:0, isStatic?1:0, .5);
|
|
7711
8493
|
this.drawFixtures(color);
|
|
7712
8494
|
}
|
|
@@ -9174,7 +9956,7 @@ class Box2dPlugin
|
|
|
9174
9956
|
queryCallback.ReportFixture = function(fixturePointer)
|
|
9175
9957
|
{
|
|
9176
9958
|
const fixture = box2d.instance.wrapPointer(fixturePointer, box2d.instance.b2Fixture);
|
|
9177
|
-
if (dynamicOnly && fixture.GetBody().GetType()
|
|
9959
|
+
if (dynamicOnly && fixture.GetBody().GetType() !== box2d.instance.b2_dynamicBody)
|
|
9178
9960
|
return true; // continue getting results
|
|
9179
9961
|
if (!fixture.TestPoint(box2d.vec2dTo(pos)))
|
|
9180
9962
|
return true; // continue getting results
|
|
@@ -9203,7 +9985,7 @@ class Box2dPlugin
|
|
|
9203
9985
|
* @param {Color} [lineColor]
|
|
9204
9986
|
* @param {number} [lineWidth]
|
|
9205
9987
|
* @param {CanvasRenderingContext2D} [context] */
|
|
9206
|
-
drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1, context
|
|
9988
|
+
drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1, context)
|
|
9207
9989
|
{
|
|
9208
9990
|
const shape = box2d.castObjectType(fixture.GetShape());
|
|
9209
9991
|
switch (shape.GetType())
|
|
@@ -9213,20 +9995,20 @@ class Box2dPlugin
|
|
|
9213
9995
|
let points = [];
|
|
9214
9996
|
for (let i=shape.GetVertexCount(); i--;)
|
|
9215
9997
|
points.push(box2d.vec2From(shape.GetVertex(i)));
|
|
9216
|
-
drawPoly(points, color, lineWidth, lineColor, pos, angle
|
|
9998
|
+
drawPoly(points, color, lineWidth, lineColor, pos, angle);
|
|
9217
9999
|
break;
|
|
9218
10000
|
}
|
|
9219
10001
|
case box2d.instance.b2Shape.e_circle:
|
|
9220
10002
|
{
|
|
9221
10003
|
const radius = shape.get_m_radius();
|
|
9222
|
-
drawCircle(pos, radius, color, lineWidth, lineColor
|
|
10004
|
+
drawCircle(pos, radius*2, color, lineWidth, lineColor);
|
|
9223
10005
|
break;
|
|
9224
10006
|
}
|
|
9225
10007
|
case box2d.instance.b2Shape.e_edge:
|
|
9226
10008
|
{
|
|
9227
10009
|
const v1 = box2d.vec2From(shape.get_m_vertex1());
|
|
9228
10010
|
const v2 = box2d.vec2From(shape.get_m_vertex2());
|
|
9229
|
-
drawLine(v1, v2, lineWidth, lineColor, pos, angle
|
|
10011
|
+
drawLine(v1, v2, lineWidth, lineColor, pos, angle);
|
|
9230
10012
|
break;
|
|
9231
10013
|
}
|
|
9232
10014
|
}
|
|
@@ -9305,7 +10087,9 @@ class Box2dPlugin
|
|
|
9305
10087
|
}
|
|
9306
10088
|
|
|
9307
10089
|
///////////////////////////////////////////////////////////////////////////////
|
|
9308
|
-
/** Box2d Init - Call with await
|
|
10090
|
+
/** Box2d Init - Call with await to init box2d
|
|
10091
|
+
* @example
|
|
10092
|
+
* await box2dInit();
|
|
9309
10093
|
* @return {Promise<Box2dPlugin>}
|
|
9310
10094
|
* @memberof Box2D */
|
|
9311
10095
|
async function box2dInit()
|
|
@@ -9368,14 +10152,14 @@ async function box2dInit()
|
|
|
9368
10152
|
{
|
|
9369
10153
|
color = getDebugColor(color);
|
|
9370
10154
|
center = box2d.vec2FromPointer(center);
|
|
9371
|
-
drawCircle(center, radius, CLEAR_WHITE, debugLineWidth, color, false, false, overlayContext);
|
|
10155
|
+
drawCircle(center, radius*2, CLEAR_WHITE, debugLineWidth, color, false, false, overlayContext);
|
|
9372
10156
|
};
|
|
9373
10157
|
debugDraw.DrawSolidCircle = function(center, radius, axis, color)
|
|
9374
10158
|
{
|
|
9375
10159
|
color = getDebugColor(color);
|
|
9376
10160
|
center = box2d.vec2FromPointer(center);
|
|
9377
10161
|
axis = box2d.vec2FromPointer(axis).scale(radius);
|
|
9378
|
-
drawCircle(center, radius, color, debugLineWidth, color, false, false, overlayContext);
|
|
10162
|
+
drawCircle(center, radius*2, color, debugLineWidth, color, false, false, overlayContext);
|
|
9379
10163
|
drawLine(vec2(), axis, debugLineWidth, color, center, 0, false, false, overlayContext);
|
|
9380
10164
|
};
|
|
9381
10165
|
debugDraw.DrawTransform = function(transform)
|
|
@@ -9421,7 +10205,7 @@ function drawNineSliceScreen(pos, size, startTile, borderSize=32, extraSpace=2,
|
|
|
9421
10205
|
}
|
|
9422
10206
|
|
|
9423
10207
|
/** Draw a scalable nine-slice UI element in world space
|
|
9424
|
-
* This function can apply color and additive color if
|
|
10208
|
+
* This function can apply color and additive color if WebGL is enabled
|
|
9425
10209
|
* @param {Vector2} pos - World space position
|
|
9426
10210
|
* @param {Vector2} size - World space size
|
|
9427
10211
|
* @param {TileInfo} startTile - Starting tile for the nine-slice pattern
|
|
@@ -9450,9 +10234,9 @@ function drawNineSlice(pos, size, startTile, color, borderSize=1, additiveColor,
|
|
|
9450
10234
|
{
|
|
9451
10235
|
// sides
|
|
9452
10236
|
const horizontal = i%2;
|
|
9453
|
-
const sidePos = cornerOffset.multiply(vec2(horizontal?i
|
|
10237
|
+
const sidePos = cornerOffset.multiply(vec2(horizontal?i===1?1:-1:0, horizontal?0:i?-1:1));
|
|
9454
10238
|
const sideSize = vec2(horizontal ? borderSize : centerSize.x, horizontal ? centerSize.y : borderSize);
|
|
9455
|
-
const sideTile = centerTile.offset(startTile.size.multiply(vec2(i
|
|
10239
|
+
const sideTile = centerTile.offset(startTile.size.multiply(vec2(i===1?1:i===3?-1:0,i===0?-flip:i===2?flip:0)))
|
|
9456
10240
|
drawTile(pos.add(sidePos.rotate(rotateAngle)), sideSize, sideTile, color, angle, false, additiveColor, useWebGL, screenSpace, context);
|
|
9457
10241
|
}
|
|
9458
10242
|
for (let i=4; i--;)
|
|
@@ -9481,7 +10265,7 @@ function drawThreeSliceScreen(pos, size, startTile, borderSize=32, extraSpace=2,
|
|
|
9481
10265
|
}
|
|
9482
10266
|
|
|
9483
10267
|
/** Draw a scalable three-slice UI element in world space
|
|
9484
|
-
* This function can apply color and additive color if
|
|
10268
|
+
* This function can apply color and additive color if WebGL is enabled
|
|
9485
10269
|
* @param {Vector2} pos - World space position
|
|
9486
10270
|
* @param {Vector2} size - World space size
|
|
9487
10271
|
* @param {TileInfo} startTile - Starting tile for the three-slice pattern
|
|
@@ -9513,7 +10297,7 @@ function drawThreeSlice(pos, size, startTile, color, borderSize=1, additiveColor
|
|
|
9513
10297
|
// sides
|
|
9514
10298
|
const a = angle + i*PI/2;
|
|
9515
10299
|
const horizontal = i%2;
|
|
9516
|
-
const sidePos = cornerOffset.multiply(vec2(horizontal?i
|
|
10300
|
+
const sidePos = cornerOffset.multiply(vec2(horizontal?i===1?1:-1:0, horizontal?0:i?-flip:flip));
|
|
9517
10301
|
const sideSize = vec2(horizontal ? centerSize.y : centerSize.x, borderSize);
|
|
9518
10302
|
drawTile(pos.add(sidePos.rotate(rotateAngle)), sideSize, sideTile, color, a, false, additiveColor, useWebGL, screenSpace, context);
|
|
9519
10303
|
}
|